JVM_类加载机制详解

Class 文件的装载流程 (类加载过程)

加载 -> 连接 (验证 -> 准备 -> 解析) -> 初始化 -> 使用 -> 卸载

加载

加载阶段,jvm 会通过类名获取到此类的字节码文件(.class 文件),
然后将该文件中的数据结构转存到内存里(转化为运行时方法区内的数据结构),
最后在堆中生成一个代表该类的 Class 对象,用于后期使用者创建对象或者调用相关方法。

验证

验证阶段用于保证 Class 文件符合 jvm 规范,如果验证失败会抛出 error。

准备

在该阶段虚拟机会给类对象的静态成员变量配置内存空间,并赋初始值。

解析

将类/接口/字段/方法中的号引用替换为直接引用。

初始化

虚拟机会调用类对象的初始化方法来进行类变量的赋值。

Class 文件被装载的条件

必须要有类去主动使用该 Class。
方式有:
使用 new 关键字、反射、克隆、反序列化;
调用类的静态方法;
调用一个类的子类的时候会初始化其父类;
包含 main() 方法的类。

被动使用则不会去装载 Class。
方式有:
调用了其父类的静态方法。

总结:

jvm 秉持了实用主义理念,对于没有用到的 Class 不会进行装载。
但是在 java 代码的启动环节会加载一些使用到的类。

加载器种类

启动类加载器(Bootstrap ClassLoader):

在 jdk8 中用来加载 jvm 自身需要的类,c++ 实现,用来加载 rt.jar。
在 jdk9 之后的 jdk 中,Bootstrap ClassLoader 主要用来加载 java.base 中的核心系统类。

扩展类加载器(ExtClassLoader):

jdk8 中用来加载 ${JAVA_HOME}/lib/ext 目录下的类。
在 jdk9 中已经被移除。

模块加载器(PlatformClassLoader):

jdk9 之后用来代替 ExtClassLoader 的加载器,用来加载 jdk 中的非核心模块类。

应用程序类加载器(AppClassLoader):

用来加载一般的应用类。

自定义加载器:

使用者自己定义的,一般继承 java.lang.ClassLoader 的类。

双亲委派机制

任意一个 ClassLoader 在尝试加载一个类的时候,都会先尝试调用其父类的相关方法去加载类,如果其父类不能加载该类,则交由子类去完成。

这样的好处:对于任意使用者自定义的 ClassLoader,都会先去尝试让 jvm 的 Bootstrap ClassLoader 去尝试加载(自定义的 ClassLoader 都继承了它们)。那么就能保证 jvm 的类会被优先加载,限制了使用者对 jvm 系统的影响。

源码

源码探究使用 jdk11,与 jdk8 中的有些许不同。

ClassLoader

ClassLoader 是类加载器的顶级父类,其核心的方法主要是 loadClass(...) 方法:

// ClassLoader.class
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    // 加锁,保证线程安全
    synchronized (getClassLoadingLock(name)) {
        // 先去找一次 class 是否已经被加载了,如果已经被加载了就不用重复加载了
        // 此方法的核心逻辑由 c++ 实现
        Class<?> c = findLoadedClass(name);
        // 没有被加载的情况
        if (c == null) {
            long t0 = System.nanoTime(); // 记录时间
            try {
                // 此处体现双亲委派机制
                // 如果该加载器存在父加载器,就会先去调用父加载器的相关方法
                // 如果没有父加载器,就去调用 Bootstrap 加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 调用 BootstrapClassLoader,此方法的核心逻辑是 c++ 实现的
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {

            }

            // 如果依旧加载不到,那么就说明父加载器仍然加载不到信息
            // 那么就需要指定的加载器自己去加载了
            if (c == null) {

                long t1 = System.nanoTime();
                
                // 该加载器加载类文件的核心逻辑
                // 该方法在 ClassLoader 中是留空的,需要子类按照自身的逻辑去实现
                c = findClass(name);

                // 此处做一些信息记录,和主逻辑无关
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        
        if (resolve) {
            // 解析 class,也是留空的,需要子类去实现
            resolveClass(c);
        }
        return c;
    }
}

BuiltinClassLoader

BuiltinClassLoader 是 jdk9 中代替 URLClassLoader 的加载器,是 PlatformClassLoader 与 AppClassLoader 的父类。其继承了 SecureClassLoader,其核心的方法主要是 loadClassOrNull(...) 方法:

// BuiltinClassLoader.class

// step 1
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{
    // 复写了 loadClass(...) 方法,但是核心是调用 loadClassOrNull(...)
    Class<?> c = loadClassOrNull(cn, resolve);
    if (c == null)
        throw new ClassNotFoundException(cn);
    return c;
}

// step 2
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
    // 加锁,保证线程安全
    synchronized (getClassLoadingLock(cn)) {
        // 先去找一次 class 是否已经被加载了,此方法是 ClassLoader 中的
        Class<?> c = findLoadedClass(cn);

        if (c == null) {

            // 这里会需要去先加载模块信息
            LoadedModule loadedModule = findLoadedModule(cn);
            if (loadedModule != null) {
                BuiltinClassLoader loader = loadedModule.loader();
                if (loader == this) {
                    if (VM.isModuleSystemInited()) {
                        c = findClassInModuleOrNull(loadedModule, cn);
                    }
                } else {
                    c = loader.loadClassOrNull(cn);
                }
            } else {

                // 先调用父加载器的相关方法去加载一次
                if (parent != null) {
                    c = parent.loadClassOrNull(cn);
                }

                // 如果没加载到,则用当前加载器去加载
                if (c == null && hasClassPath() && VM.isModuleSystemInited(){
                    // 此方法内会调用到 defineClass(...) 方法去加载类文件
                    c = findClassOnClassPathOrNull(cn);
                }
            }

        }

        // 解析 class
        if (resolve && c != null)
            resolveClass(c);

        return c;
    }
}

该加载器中还有一个加载 class 字节码的方法:

// BuiltinClassLoader.class
private Class<?> defineClass(String cn, Resource res) throws IOException{
    
    URL url = res.getCodeSourceURL();

    // 先解析这个 class 的路径
    int pos = cn.lastIndexOf('.');
    if (pos != -1) {
        String pn = cn.substring(0, pos);
        Manifest man = res.getManifest();
        defineOrCheckPackage(pn, man, url);
    }

    // 这里会将 class 读取出来成一个 byte[] 字符串,并通过 jvm 的相关方法去加载
    ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        // 该方法最后会调用 ClassLoader 内的 native 方法
        return defineClass(cn, bb, cs);
    } else {
        byte[] b = res.getBytes();
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        // 该方法最后会调用 ClassLoader 内的 native 方法
        return defineClass(cn, b, 0, b.length, cs);
    }
}

BootClassLoader

BootClassLoader 是 ClassLoaders 的一个静态内部类,虽然它从代码实现上是 BuiltinClassLoader 的子类,但是从功能上说它是 PlatformClassLoader 的 parent 类:

// ClassLoader.class
private static class BootClassLoader extends BuiltinClassLoader {
    BootClassLoader(URLClassPath bcp) {
        super(null, null, bcp);
    }

    // 复写了 BuiltinClassLoader 中的 loadClassOrNull(...) 方法
    @Override
    protected Class<?> loadClassOrNull(String cn) {
        return JLA.findBootstrapClassOrNull(this, cn);
    }
};

PlatformClassLoader

PlatformClassLoader 也是 ClassLoaders 的一个静态内部类,从功能上说它是 BootClassLoader 的子类,同时也是 AppClassLoader 的 parent 类。PlatformClassLoader 主要用来加载一些 module:

// ClassLoader.class
private static class PlatformClassLoader extends BuiltinClassLoader {
    static {
        if (!ClassLoader.registerAsParallelCapable())
            throw new InternalError();
    }

    // 此处会将 BootClassLoader 作为 parent 参数传入进去
    PlatformClassLoader(BootClassLoader parent) {
        super("platform", parent, null);
    }

    // 加载 module
    private Package definePackage(String pn, Module module) {
        return JLA.definePackage(this, pn, module);
    }
}

AppClassLoader

AppClassLoader 的核心方法是 loadClass(...),最终会调用到 BuiltinClassLoader.loadClassOrNull(...) 方法,而此方法内部又会调用到 PlatformClassLoader.loadClass(...) 方法;然后实际上 PlatformClassLoader 内部又会去调用 BootClassLoader 的 loadClassOrNull(...) 方法。这种方式下就完成类加载器的双亲委派机制:

// ClassLoader.class
private static class AppClassLoader extends BuiltinClassLoader {
    static {
        if (!ClassLoader.registerAsParallelCapable())
            throw new InternalError();
    }

    final URLClassPath ucp;

    // 此处会将 PlatformClassLoader 作为 parent 参数传入进去
    AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) {
        super("app", parent, ucp);
        this.ucp = ucp;
    }

    @Override
    protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            int i = cn.lastIndexOf('.');
            if (i != -1) {
                sm.checkPackageAccess(cn.substring(0, i));
            }
        }
        // 实际上是调用了 BuiltinClassLoader.loadClassOrNull(...) 方法
        return super.loadClass(cn, resolve);
    }

    @Override
    protected PermissionCollection getPermissions(CodeSource cs) {
        PermissionCollection perms = super.getPermissions(cs);
        perms.add(new RuntimePermission("exitVM"));
        return perms;
    }


    void appendToClassPathForInstrumentation(String path) {
        ucp.addFile(path);
    }


    private Package definePackage(String pn, Module module) {
        return JLA.definePackage(this, pn, module);
    }


    protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {
        return super.defineOrCheckPackage(pn, man, url);
    }
}
我来评几句
登录后评论

已发表评论数()

相关站点

热门文章