zoukankan      html  css  js  c++  java
  • Java-JVM 类加载机制

    类的生命周期中的第一步,就是要被 JVM 加载进内存,类加载器就是来干这件事。

    一、类加载器种类

    系统提供了 3 种类加载器:

    1.启动类加载器(Bootstrap ClassLoader)
    由 C 和 C++ 编写,是在 JVM 启动后初始化的。可在这里查看到源码(OpenJDK):http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/933f6b06c1cb/src/share/native/java/lang/ClassLoader.c
    负责将存放在 <JAVA_HOME>jrelib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且能被虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。
    
    2.扩展类加载器(Extension ClassLoader)
    由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>jrelibext 目录中的所有类库,以及系统变量 java.ext.dirs 指定路径中的所有类库,开发者可以直接使用扩展类加载器。
    
    3.应用程序类加载器(Application ClassLoader)
    由 sun.misc.Launcher$AppClassLoader 实现,可以通过 ClassLoader 类中的 getSystemClassLoader() 方法的获得,所以一般也称它为“系统类加载器”。
    它负责加载用户类路径(classpath:CLASSPATH 环境变量指定的, 由 -classpath 或 -cp 选项定义的,或者是 jar 中的 Manifest 的 classpath 属性定义的)上所指定的类库,以及系统变量 java.class.path 指定路径中的所有类库。
    开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器(父子关系一般不会以继承的关系实现,而是以组合关系来复用父加载器的代码),结构如图:

    getParent() 可获得父加载器

    public class ClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            // 默认由 AppClassLoader 加载类
            System.out.println(classLoader);
            // ExtClassLoader
            System.out.println(classLoader.getParent());
            // Bootstrap ClassLoader,由 JVM 启动
            System.out.println(classLoader.getParent().getParent());
        }
    }
    View Code

    除系统提供的加载器外,还可以自己定义类加载器。(继承 java.lang.ClassLoader 类实现)

    二、类加载器工作方式(加载机制)

    2.1.委托机制(委派模型 或 父委派模型)

    委派模型是描述类加载器之间的层次关系。

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。

    因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(找不到所需的类)时,子加载器才会尝试自己去加载。

    在 java.lang.ClassLoader 中的 loadClass() 方法中实现该了过程。

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查是否已加载该类
            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) {
                    // 如果从非 null 的父类加载器中找不到该类,则抛出 ClassNotFoundException
                }
    
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 如果仍未找到,则调用 findClass 查找该类
                    c = findClass(name);
    
                    // 这是定义的类加载器; 记录统计数据
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                // 解析类,属于类加载的 link 阶段
                resolveClass(c);
            }
            return c;
        }
    }
    
    /**
     * ClassLoader 的子类建议重写 findClass 方法,而不是 loadClass
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
    View Code

    自己写的 java.lang.String 类,是否可以替换 JDK 自带的类?

    答案是不行的。但这非委托机制解决的,因为委托机制是可以被打破的,完全可以写一个 classLoader 来加载自己写的 java.lang.String 类。

    但是你会发现也加载不成功,因为 JVM 的实现中已经保证了 java.* 开头的类必须由 bootstrp 来加载。
     

    2.2.可见性机制

    子类加载器可以看到父类加载器加载的类,而反之则不行。当 Abc.class 已经被 Application 类加载器加载过了,然后想要使用 Extension 类加载器加载这个类,将会抛出 java.lang.ClassNotFoundException 异常。

    2.3.单一性机制

    根据委托机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写 loadClass() 的类加载器可以做到不遵守委托机制和单一性机制,但这样做并不可取。

    判断类是否“相等”

    任意一个类,都由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。

    因此,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等。

    这里的“相等”,包括代表类的 Class 对象的 equals() 方法、isInstance() 方法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况。

    关于破坏委派模型

    java 引入了线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过 Thread 类的 setContextClassLoader 进行设置,默认继承父线程类加载器,也可由父类加载器请求子类加载器完成类加载动作。

    https://www.jianshu.com/p/09f73af48a98


    https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

    https://docs.oracle.com/javase/tutorial/ext/basics/load.html

    https://blog.csdn.net/lengxiao1993/article/details/86689331

    https://github.com/doocs/jvm/blob/master/docs/10-class-loader.md

  • 相关阅读:
    最长回文子串 V2(Manacher算法)
    用例建模Use Case Modeling
    分析一套源代码的代码规范和风格并讨论如何改进优化代码
    结合工程实践选题调研分析同类软件产品
    如何提高程序员的键盘使用效率
    antd移动端onClick事件点击无效
    webpack打包问题
    centos下部署项目问题
    javascript return 跟 break区别
    VUE清除组件内部定时器
  • 原文地址:https://www.cnblogs.com/jhxxb/p/10914456.html
Copyright © 2011-2022 走看看