ClassLoader 中除了“双亲委派”,这些细节更应该了解

提到 ClassLoader,最先想到的一定是“双亲委派”了,加载类时优先使用父类加载器(parent classloader),不过除了这个委托模型之外,还有很多细节值得研究

加载时机

除了显示调用 ClassLoader.loadClass 进行加载 Class 之外,JVM 在下面的5种场景下,也会执行加载 Class 的操作(由 JVM 调用 ClassLoader.loadClassInternal)

  1. 使用 new 关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

以上几种加载时机,统称为主动引用的方式;除此之外,其他引用类的方式都不会被触发 Class 的加载

比如下面这种情况,就不属于主动引用,不会进行类的加载

Class.forName

通过 Class.forName 的形式,可以进行进行类的加载,不过这里使用的是调用者(caller)的类加载器,也就是发起Class.forName 方法调用的类的类加载器

类加载器 的“传递性”

首先说一下两个概念,定义加载器(defining loader)初始加载器(initiating loader)

现有一个 ClassLoader A,如果通过A直接定义(而不是在A内委托 parent classLoader 加载)了一个 Class X,那么称 ClassLoader A 是 Class X 的定义加载器,也称 ClassLoader A定义了 Class X

当一个 ClassLoader 委托 parent classLoader 进行加载某类(loadClass),那么此时 loadClass 的 classLoader 和实际上 defineClass 的 parent classLoader 其实并不是同一个

现有一个 ClassLoader B,通过 ClassLoader B 的 loadClass 方法加载 Class Y,无论ClassLoader B 是直接 define了Class Y,还是委托 parent classLoader 去 define 了Class Y,那么 ClassLoader B 都是 Class Y的 初始类加载器

只不过如果 Class Y 是由 ClassLoader B 直接加载的,那么 Class Y的定义加载器初始加载器 都是ClassLoader B;如果是委托父类 parent classLoader C加载的,那么定义加载器就是ClassLoader C,而初始加载器就是 ClassLoader B

如下图所示,对于被加载的Class X来说,ClassLoader A就是初始加载器,而实际上 ClassLoader A是委托了 ClassLoader B 去完成 define 的,所以 ClassLoader B 是定义加载器 image.png

使用 Class.forName 这种形式加载的 Class,实际上也是使用调用者(caller)的定义加载器

这段虽然像绕口令一样……但这也是 ClassLoader 的关键,ClassLoader 的传递特性非常重要


对于上面提到的 “主动引用” 方式的加载Class,在加载机制上有一些细节需要注意:

  • JVM在解释 Class 时实际上 是 “惰性加载” 的,在解释执行的行遇到引用后才会解析引用的 Class;比如在方法体中调用了某类,只有在执行到这一行时才会进行加载
  • 对于同一个 ClassLoader 实例来说,在当前 Class 中没有加载过的 Class,会使用发起引用类的定义加载器(而不是 system ClassLoader) 进行加载
  • 如果一个 ClassLoader 中已经加载过某个 Class,那么就不会再加载(连 loadClass 都不会调用);比如 Class A里引用了 Class X 和 Class B,Class B 里也引用了 Class X,那么在执行 Class B 时,不会再发生 loadClass(X) 的操作

根据上面几个特点,自定义 ClassLoader 就比较简单了,比如 Spring Boot 提供的可执行Jar(Executable Jar)的 ClassLoader 实现中:

只需要创建一个负责加载 jar 包内 jar 包的 ClassLoader(org.springframework.boot.loader.JarLauncher),然后入口类中通过该 ClassLoader 去加载我们代码中的 Main-Class 即可,这样就可以做到加载 jar 包中的 jar 包了:

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java双亲委派机制是一种类加载机制,用于保证Java类的安全和避免类的重复加载。它是由Java类加载器(ClassLoader)实现的。 当一个类需要被加载时,Java类加载器会按照双亲委派的方式进行加载,即先委托给父类加载器尝试加载,如果父类加载器无法加载,则由当前类加载器自己加载。 具体来说,当加载一个类时,Java类加载器会按照以下顺序进行尝试加载: 1. 首先,检查当前类是否已经被加载过了,如果已经加载过了,则直接返回该类的Class对象。 2. 如果当前类还没有被加载过,那么会将加载请求委托给父类加载器。每个类加载器都有一个父类加载器(除了启动类加载器),它们之间形成了一条层次关系。 3. 父类加载器会尝试加载该类。如果父类加载器可以成功加载,则返回该类的Class对象。如果父类加载器无法加载,那么当前类加载器会尝试自己加载。 4. 当前类加载器会从指定的路径或者classpath查找该类的字节码文件,并将其转换为Class对象。 5. 如果当前类加载器仍然无法加载该类,则将加载请求再次委托给父类加载器的父类加载器。这个过程一直循环下去,直到达到启动类加载器为止。 通过这种双亲委派机制,Java可以确保类的加载是由上至下的,避免了重复加载和类的安全问题。例如,如果一个类已经被父类加载器加载了,那么子类加载器再次加载时会直接使用父类加载器已经加载好的Class对象,避免了重复加载。 总结起来,Java双亲委派机制是一种类加载机制,它通过委托父类加载器来尝试加载类,避免了重复加载和类的安全问题。这种机制使得类的加载具有层次性,保证了类的一致性和唯一性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值