深入理解JVM类加载机制

Java应用程序启动过程

public class HelloWorld {
    
    public void sayHello(){
        System.out.println("hello world!");
    }
    
    public static void main(String[] args){
        new HelloWorld().sayHello();
    }
}
复制代码

当我们使用java HelloWorld运行以上程序时,发生了什么?

image.png

创建引导类加载器

Windows环境下调用jvm.dll库创建BootstrapClassLoader引导类加载器,这是一个C++实例(主要用来加载jdk/jre/lib目录下的jar类库,也称VM内置类加载器),所以如果你妄图通过Java程序打印它会发现结果是null

public class BootstrapClassLoaderPrintLab {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
    }
}
复制代码

初始化系统类加载器

在运行时启动序列中,会触发ClassLoader#initSystemClassLoader的首次调用:

private static ClassLoader scl;
private static boolean sclSet;

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    return scl;
}

// is typically the class loader used to start the application.
// This method is first invoked early in the runtime's startup sequence
private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        scl = l.getClassLoader();
        sclSet = true;
    }
}
复制代码

接着sun.misc.Launcher#getLauncher会被调用:

private static Launcher launcher = new Launcher();
private ClassLoader loader;

public static Launcher getLauncher() {
    return launcher;
}

public Launcher() {
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}

public ClassLoader getClassLoader() {
    return this.loader;
}
复制代码

Launcher构造方法中,创建了ExtClassLoader扩展类加载器和AppClassLoader应用程序类加载器,后者通过构造方法传参保留了前者的引用,跟踪调用链不难发现后者通过ClassLoader parent属性保留了对前者的委托引用。

小结:ClassLoader#sclLauncher#loader保存了应用程序类加载器实例的引用

加载应用程序启动类

通过ClassLoader#initSystemClassLoader获取已初始化好的系统类加载器,此刻也即AppClassLoader,调用其loadClass方法对HelloWorld.class进行加载:

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    // 忽略校验检查相关代码

    if (this.ucp.knownToNotExist(var1)) {
        Class var5 = this.findLoadedClass(var1);
        if (var5 != null) {
            if (var2) {
                this.resolveClass(var5);
            }

            return var5;
        } else {
            throw new ClassNotFoundException(var1);
        }
    } else {
        return super.loadClass(var1, var2);
    }
}
复制代码

通过断点调试(debug condition: var1.equals("HelloWorld"))发现其委托父类URLClassLoader处理,并继续委托父类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();

            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
复制代码

查看其方法注释:

加载具有指定二进制名称的类。 此方法的默认实现按以下顺序搜索类:1.调用findLoadedClass(String)以检查类是否已加载。2.在父类加载器上调用loadClass方法。 如果parentnull,则使用虚拟机内置的类加载器。3.调用findClass(String)方法来查找类

如此类加载器的层次结构以及双亲委派机制逐步浮出水面:

image.png

JDK通过ClassLoader#loadClass内置了双亲委派机制;鼓励开发者重写ClassLoader#findClass来实现自己的类资源查找逻辑

类加载过程:

  • 加载:通过类全限定名定位其二进制字节流资源位置,并读取到本地内存字节数组中
  • 验证:二进制字节流格式需符合Java虚拟机规范,例如开头为CAFEBABE、各元数据段字节排列是否正确(如常量池元数据段分为常量池个数和常量实体数组两部分,个数需和数组大小对应;常量实体应以常量内容及其对应字节长度等)。类似于协议报文内容编排需要符合对应协议规定的报文格式要求。
  • 解析:静态链接,例如将final/static方法的符号引用解析为直接引用(代码段地址)
  • 准备:例如将类变量赋予类型对应的默认值
  • 初始化:例如执行静态变量的显式赋值、静态代码块

类被加载到方法区后主要包括以下信息:

  • 运行时常量池
  • 类型信息:例如类名、类访问修饰符、是否final、继承/实现了哪些类
  • 字段信息:字段类型、字段名称、字段访问修饰符、finalstatic
  • 方法信息:返回值类型、方法名称、参数类型列表、访问修饰符、finalstatic
  • 加载该类的类加载器对象引用
  • Class对象引用:在类被加载到方法区后,会在堆中相应创建一个Class对象作为应用程序访问该类元数据的入口

执行main方法

由JVM触发。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享