JVM启动及类加载

原料

  1. Win10操作系统,安装好JDK,配置好环境变量: JAVA_HOME, PATH, CLASSPATH
  2. 在D:\test下,写一个HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}
  1. javac编译HelloWorld.java得到HelloWorld.class
  2. 使用命令:java HelloWorld 运行该类,得到输出结果:Hello World!

java HelloWorld 命令执行过程分析

  1. 这个命令里的java就是位于JAVA_HOME/bin下的java.exe可执行程序
  2. 这个命令里的HelloWorld是给java.exe传递的参数,代表的是HelloWorld.class文件
  3. 当执行该命令时,Win10操作系统调用java.exe可执行程序,并给它传递参数HelloWorld
  4. java.exe会启动一个JVM(由C++编写)
  5. JVM里面会启动一个引导类加载器实例(由C++编写)
  6. 引导类加载器实例会加载一个由Java编写的类sun.misc.Launcher的实例
  7. Launcher实例会加载另外两个由Java编写的类加载器:ExtClassLoader,AppClassLoader
  8. AppClassLoader会加载标题中的给java命令传递进来的参数HelloWorld所代表的类
  9. 它会读取HelloWorld.class文件,根据里面信息,将类加载进方法区,并在堆里面生成这个类的Class实例
  10. JVM执行这个类里面的main方法
  11. 最后销毁JVM

三个类加载器之间的关系

  1. 引导类加载器由C++实现,其实例由JVM生成
  2. ExtClassLoader和AppClassLoader由Java实现,他们的实例由sun.misc.Launcher生成,他们也是写在sun.misc.Launcher类里面的两个静态类
  3. ExtClassLoader和AppClassLoader都继承自java.net.URLClassLoader --> java.security.SecureClassLoader --> java.lang.ClassLoader
  4. 三个类加载器的关系是父子关系,AppClassLoader里面的parent属性指向ExtClassLoader,ExtClassLoader里面的parent属性指向null,这里用null表示“引导类加载器”(由C++实现,在Java中没有对应的对象,就用null来表示)
  5. 在Launcher中定义的属性ClassLoader loader,赋的值是AppClassLoader实例。AppClassLoader实例化时传入的参数是一个ExtClassLoader实例,在ExtClassLoader实例化时传入的是null。这个传入的参数表示的是它的父加载器。传入null时,表示它的父加载器是引导类加载器
public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

类的加载过程

  1. 类的加载属于懒加载,只有被用到时才会被加载
  2. 加载:ClassLoader首先会去CLASSPATH下查找该类的.class文件,读取该文件信息
  3. 验证:对读取到的信息进行格式校验,校验格式是否符合.class文件的要求
  4. 准备:给该类的每一个静态变量分配内存,并赋默认值
  5. 解析:把对静态方法的符号引用替换为对其内存地址的直接引用
  6. 初始化:把该类的静态变量初始化为指定的值,然后执行该类的静态代码块
  7. 类加载完成之后主要信息是放在方法区,包括了该类的运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应的Class实例的引用
  8. 在堆中也会生成该类对应的Class实例,以供开发人员对该类进行访问和调用

类加载时用到的的两个核心方法

  1. loadClass(在java.lang.ClassLoader中)
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  1. findClass(在java.net.URLClassLoader中)
    protected Class<?> findClass(final String name) throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

类加载时的双亲委派机制

  1. 在每个类加载器中都有一个缓存容器用来存放该类加载器加载过的类。从类加载的核心方法loadClass中可以看到,首先调用findLoadedClass方法,查看在本加载器的已加载类缓存容器中,是否存在该类。如果存在该类就会返回该类,否则进入下面的if代码块,以继续加载该类。
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
  1. 在下面的代码中可以看到,当该类的parent存在时就先由parent进行类的加载。parent==null表示,该类加载器的parent是引导类加载器(由C++实现,没有Java对象与之对应,所以为null,下面的findBootstrapClassOrNull方法就是用引导类加载器加载该类)
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
  1. 在parent没有加载到该类的时候,就由自己来加载:
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
  1. 因为JDK默认ClassLoader loader,赋的值是AppClassLoader实例,所有首先进入该实例的loadClass方法去加载类,然后重复上面的1,2,3步骤进行类的加载。这样就形成了以下双亲委派机制加载流程:(自己的缓存容器中不存在就先委派自己的父加载器进行加载,父加载器返回null时,自己再亲自加载并存储到自己的缓存容器中,根加载器没有父,不再向上委派)
    image

双亲委派机制的作用

  1. 保证加载类的优先级顺序:引导类加载器 --> ExtClassLoader --> AppClassLoader --> 自己写的类加载器
  2. 因为引导类加载器负责加载JDK核心类,ExtClassLoader负责加载JDK扩展类,这些重要的类一旦被加载完成,其他子加载器是不能再次加载的。保证了这些重要的类是不能被同名类覆盖的。
  3. 例如黑客把他自己写的java.lang.String类置入我们打包后的项目包中,等项目包发布到服务器上,这个类是不能加载进入JVM的!JVM只会加载服务器上提前安装好的JDK里面的java.lang.String类。
  4. 每个类第一次加载时会走双亲委派机制,一旦加载进JVM,下一次再使用时会直接从每个加载器的缓存容器里取到。
  5. 因为大量的类是项目上自己写的类,这些类都是由AppClassLoader或者自己写的类加载器进行加载,后期使用时直接从AppClassLoader或者自己写的类加载器的缓存容器中读取,效率也很好!
posted @ 2021-07-05 19:29  GabeChen  阅读(46)  评论(0)    收藏  举报