资讯专栏INFORMATION COLUMN

JVM_类加载机制详解

MasonEast / 3440人阅读

摘要:加载器种类启动类加载器在中用来加载自身需要的类,实现,用来加载。那么就能保证的类会被优先加载,限制了使用者对系统的影响。这种方式下就完成类加载器的双亲委派机制此处会将作为参数传入进去实际上是调用了方法

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);
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76281.html

相关文章

  • Java面试 32个核心必考点完全解析

    摘要:如问到是否使用某框架,实际是是问该框架的使用场景,有什么特点,和同类可框架对比一系列的问题。这两个方向的区分点在于工作方向的侧重点不同。 [TOC] 这是一份来自哔哩哔哩的Java面试Java面试 32个核心必考点完全解析(完) 课程预习 1.1 课程内容分为三个模块 基础模块: 技术岗位与面试 计算机基础 JVM原理 多线程 设计模式 数据结构与算法 应用模块: 常用工具集 ...

    JiaXinYi 评论0 收藏0
  • Java学习路线总结,搬砖工逆袭Java架构师(全网最强)

    摘要:哪吒社区技能树打卡打卡贴函数式接口简介领域优质创作者哪吒公众号作者架构师奋斗者扫描主页左侧二维码,加入群聊,一起学习一起进步欢迎点赞收藏留言前情提要无意间听到领导们的谈话,现在公司的现状是码农太多,但能独立带队的人太少,简而言之,不缺干 ? 哪吒社区Java技能树打卡 【打卡贴 day2...

    Scorpion 评论0 收藏0
  • Java经典

    摘要:请注意,我们在聊聊单元测试遇到问题多思考多查阅多验证,方能有所得,再勤快点乐于分享,才能写出好文章。单元测试是指对软件中的最小可测试单元进行检查和验证。 JAVA容器-自问自答学HashMap 这次我和大家一起学习HashMap,HashMap我们在工作中经常会使用,而且面试中也很频繁会问到,因为它里面蕴含着很多知识点,可以很好的考察个人基础。但一个这么重要的东西,我为什么没有在一开始...

    xcold 评论0 收藏0
  • 四年来Android面试大纲,作为一个Android程序员

    摘要:再附一部分架构面试视频讲解本文已被开源项目学习笔记总结移动架构视频大厂面试真题项目实战源码收录 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava异常详解Java抽象类和接口的区别Java深拷贝和浅拷...

    不知名网友 评论0 收藏0
  • Java加载详解

    摘要:当一个文件是通过网络传输并且可能会进行相应的加密操作时,需要先对文件进行相应的解密后再加载到内存中,这种情况下也需要编写自定义的并实现相应的逻辑 Java虚拟机中的类加载有三大步骤:,链接,初始化.其中加载是指查找字节流(也就是由Java编译器生成的class文件)并据此创建类的过程,这中间我们需要借助类加载器来查找字节流. Java虚拟机默认类加载器 Java虚拟机提供了3种类加载器...

    Baaaan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<