Java-JVM 类加载器与类加载机制

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

 

一、类加载器种类

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

1.启动类加载器(Bootstrap ClassLoader)
由 C 和 C++ 编写,是在 JVM 启动后初始化的。可在这里查看到源码(OpenJDK):https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/share/native/java/lang/ClassLoader.c
负责将存放在 <JAVA_HOME>\jre\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且能被虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。

2.扩展类加载器(Extension ClassLoader)
由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>\jre\lib\ext 目录中的所有类库,以及系统变量 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

https://blog.csdn.net/f641385712/article/details/86715906

posted @ 2019-05-24 15:14  江湖小小白  阅读(481)  评论(0编辑  收藏  举报