JVM二:类加载器和双亲委派模型

类加载器

根据 JVM 规范分为启动类加载器(Bootstrap ClassLoader)和非启动类加载器。
在这里插入图片描述

自定义一个类加载器

类加载器的命名空间:每个类加载器都有属于自己的命名空间,一个限定名类理论上只会被一个类加载器加载。通过以下代码验证,同一个限定名类被不同类加载器加载后,被 JVM 认为这是两个完全不一样的 Class

 public static void main(String[] args) throws Exception {
        // 自定义实现 classloader ,通过重写 java.lang.ClassLoader 的 loadClass 方法然后调用父类的 defineClass 进行加载
        ClassLoader customClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    // 获取文件名
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    // 读取文件流
                    InputStream resourceAsStream = getClass().getResourceAsStream(fileName);
                    if (resourceAsStream == null) {
                        return super.loadClass(name);
                    }
                    byte b[] = new byte[resourceAsStream.available()];
                    resourceAsStream.read(b);
                    // defineClass 的修饰符为 final,因此自定义 ClassLoader 只能在加载阶段搞事情,
                    // 说明一件事情,除了 Bootstrap ClassLoader,所有通过继承 java.lang.ClassLoader 的类加载器	
                    // 只能自定义其加载范围和加载方法,最终都得去执行 defineClass
                    return defineClass(name, b, 0, b.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
        };
        // 使用自定义的 customClassLoader 和 默认的 ApplicationClassLoader 分别对 Test_2 进行加载并进行类型判断
        Object obj = customClassLoader.loadClass("com.dailycode.jvm.Test_2").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof com.dailycode.jvm.Test_2);
    }

loadClass 加载字节码文件
defineClass 将给定的字节数组转化为 Class 实例

双亲委派模型

为什么要使用双亲委派模型

  • 避免重复加载
  • 防止核心类库的破坏,万一有人重写了 Object 类并放置了恶意代码

源码解读

// java.lang.ClassLoader.loadClass 源代码
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);	// 1)验证是否已经被加载过
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) { // 2)parent != null 则尝试由 parent 去加载
                        c = parent.loadClass(name, false);
                    } else { // 3)parent = null 说明 parent 为 Bootstrap ClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) { // 4)parent 无法加载,由当前 ClassLoader 去加载
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • 会先验证是否已经被加载过,如果没有加载过则会先去找父加载器进行加载,如果父加载器无法加载则由当前 ClassLoader 进行加载。
    在向上寻找加载器的过程中,当发现获取的 parent 为 null,此时 parent 为 Bootstrap ClassLoader ,启动类加载器由 JVM 管理无法被应用程序作为对象应用所以为 null。
  • 关于父加载器的说法,它们之间并不是继承关系,仅仅代表的是层级,越是顶层的类库越是会被高层级的类加载器进行加载。

双亲委派模型的破坏

第一次的破坏产生

上文通过重写 loadClass 自定义类加载器,导致一个限定名类被两个类加载器加载,这就造成了双亲委派模型在初始阶段就遭到了破坏。loadClassJDK 最开始就有了,而双亲委派模型是 JDK1.2 引入的,因为 JDK 是向下兼容的,也就导致这个方法不可以像 defineClass 一样用 final 修饰。为了弥补 loadClass的造成的缺陷,在引入双亲委派模型的同时添加了一个 findClass 的方法。

public static void main(String[] args) throws Exception {
        // 自定义实现 classloader ,通过重写 java.lang.ClassLoader 的 findClass 方法然后调用父类的 defineClass 进行加载
        // 验证是否会破坏双亲委派模型
        ClassLoader customClassLoader = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    // 获取文件名
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    // 读取文件流
                    InputStream resourceAsStream = getClass().getResourceAsStream(fileName);
                    if (resourceAsStream == null) {
                        return super.loadClass(name);
                    }
                    byte b[] = new byte[resourceAsStream.available()];
                    resourceAsStream.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
        };
        // 使用自定义的 customClassLoader 和 默认的 ApplicationClassLoader 分别对 Test_2 进行加载并进行类型判断
        Object obj = customClassLoader.loadClass("com.dailycode.jvm.Test_2").newInstance();
        System.err.println(obj.getClass());
        System.err.println(obj instanceof com.dailycode.jvm.Test_2);
    }

findClass 是在 JVM 内部实现的查找,依托于双亲委派模型自下而上的找寻 ClassLoader 去加载 .class 文件。loadClass 是实现双亲委派模型的核心代码,它在最后调用了 findClass 方法。所以重写 findClass 不会破坏双亲委派模型

protected Class<?> findClass(String name) throws ClassNotFoundException {
 	throw new ClassNotFoundException(name);
}
// 分别打印出 loadClass 和 findClass 两种方式使用的类加载器
Class<?> aClass = customClassLoader.loadClass("com.dailycode.jvm.Test_2");
System.out.println(aClass.getClassLoader());

第二次的破坏产生

JAVA 的 SPI 机制,可理解为一种服务发现机制亦各类服务都注册到 JDK 提供的接口上,上层在调用 JDK 接口时去选择用哪一个实现来加载,亦或理解为一种策略模式,根据配置来决定运行时接口的实现类是哪个。
假设 JDBC 就是 SPI 机制下的一类接口,然后 MySQL、H2 都实现了这一个接口,在加载的过程中,Bootstrap ClassLoader 委派了 Application ClassLoader 去加载,根据双亲委派模型的设计,是采用自下而上的模式进行加载的,而在 SPI 的设计中采用了上层加载器委派了下层加载器。

第三次破坏产生

热部署

思考

Class.forName(xx)、loadClass(xx) 的区别

public static Class<?> forName(String className) throws ClassNotFoundException {
       Class<?> caller = Reflection.getCallerClass();
       return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

Class.forName() 调用了一个 native 方法 forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) 可以看出,入参中有一个 initialize 代表是否初始化是写死的为 true ,也就意味着一定会加载静态变量和静态代码块。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值