JVM的类加载机制——除了双亲委派

背景介绍

网上很多说法讲JVM的双亲委派模型进行类加载,但是一直有个疑问就是在双亲委派之前JVM如何决定双亲委派的起点,尤其是在自定义类加载器的前提下,

  1. 这么多同级的类加载器,JVM怎么决定从哪个加载器入手呢?
  2. 同时如果一个类里面加载了另外一个类,这时候JVM又是选取那个类加载器进行加载类的呢?

参考资料

今天看到一篇CSDN的文章,里面在一处地方有提到一个 

[https://blog.csdn.net/m0_38075425/article/details/81627349](jvm之java类加载机制和类加载器(ClassLoader)的详解)

1.JVM的类加载机制主要有如下3种。

- 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
- 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
- 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
————————————————
版权声明:本文为CSDN博主「超级战斗王」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_38075425/article/details/81627349

可以看到第一点所说"全盘负责"的原则,总算解释了心理的疑问

代码验证

两个辅助测试的类

package com.example;

/**
 * @ClassName ClassA
 * @Author lianxiaodong
 * @Date 2021年12月10日 下午1:13
 */
public class ClassA {

    // 打印本类的加载器类型
    public void printClassLoader() {
        System.out.println("ClassA's class loader is:" + getClass().getClassLoader());
        new ClassB().printClassLoader();
    }

}
View Code
package com.example;

/**
 * @ClassName ClassB
 * @Author lianxiaodong
 * @Date 2021年12月10日 下午1:13
 */
public class ClassB {

    // 打印本类的加载器类型
    public void printClassLoader() {
        System.out.println("ClassB's class loader is:" + getClass().getClassLoader());
    }

}
View Code

测试的主程序

package com.example;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * jvm类加载器实验
 *
 * @ClassName ClassLoaderTest
 * @Author lianxiaodong
 * @Date 2021年12月10日 下午12:39
 */
public class ClassLoaderTest {

    // 自定义类加载器
    static class MyClassLoader extends ClassLoader{
        /**
         * 直接覆盖loadClass方法,而不是建议的findClass方法,要不然默认走双亲委派会得不到正确的实验结论
         */
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            try {
                // 标识类加载时机
                System.out.println("MyClassLoader load class: " + name);
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream is = ClassLoaderTest.class.getResourceAsStream(fileName);
                if (is == null) {
                    return super.loadClass(name);
                }
                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
                e.printStackTrace();
                throw new ClassNotFoundException(name);
            }
        }

    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 默认情况下选用的应用类加载器
        new ClassA().printClassLoader();
        System.out.println("================");
        // 使用自定义类加载器加载ClassA
        Class<ClassA> loadClass = (Class<ClassA>) new MyClassLoader().loadClass("com.example.ClassA");
        // 利用反射获取ClassA实例
        Object o = loadClass.newInstance();
        // 打印反射获取的实例的类型
        System.out.println(o.getClass());
        // 判断自定义加载的类加载器加载的ClassA的实例是否是默认类加载器(应用类加载器)加载的ClassA的实例(false),
        // 这里说明类的唯一性判定是 全限定名+类加载器来决定的
        System.out.println("o instanceof ClassA:" + (o instanceof ClassA));
        System.out.println("================");
        // 自定义类加载器加载的ClassA实例如果引用了ClassB会由谁加载的验证,结果符合"全盘负责"原则
        // 这里必须使用反射进行测试,如果使用强制类型转换 (ClassA)o
        // ClassA a = (ClassA) o;
        // 会报错,Exception in thread "main" java.lang.ClassCastException: com.example.ClassA cannot be cast to com.example.ClassA
        // 具体原因是因为JVM认为两个类型的加载器不是同一个,不能够转换
        Method printClassLoader = loadClass.getMethod("printClassLoader");
        printClassLoader.invoke(o);
        // 下面验证Class.forname()方法的默认加载器类型,其实看源码也知道,是同类的类加载器,符合"全盘负责"原则
        //System.out.println("============");
        //Class<?> aClass = Class.forName("com.example.ClassA");
        //o = aClass.newInstance();
        //printClassLoader = aClass.getMethod("printClassLoader");
        //printClassLoader.invoke(o);

    }

}

这里偷个懒,具体的每行代码的具体作用都有写上去,也就不赘述了,下面贴一下在我机器上的运行结果

ClassA's class loader is:sun.misc.Launcher$AppClassLoader@18b4aac2
ClassB's class loader is:sun.misc.Launcher$AppClassLoader@18b4aac2
================
MyClassLoader load class: com.example.ClassA
MyClassLoader load class: java.lang.Object
class com.example.ClassA
o instanceof ClassA:false
================
MyClassLoader load class: java.lang.System
MyClassLoader load class: java.lang.StringBuilder
MyClassLoader load class: java.lang.Class
MyClassLoader load class: java.io.PrintStream
ClassA's class loader is:com.example.ClassLoaderTest$MyClassLoader@78e03bb5
MyClassLoader load class: com.example.ClassB
ClassB's class loader is:com.example.ClassLoaderTest$MyClassLoader@78e03bb5

结论

其实结论很明显了,结果就是JVM的类加载除了双亲委派,为了逻辑完整,还得再加一条就是“全盘负责”

 

posted @ 2021-12-10 14:54  MarshWinter  阅读(78)  评论(0编辑  收藏  举报