我竟然被「双亲委派」给虐了

什么是双亲委托

首先,我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当 JVM 想要加载一个 .class 文件的时候,到底应该由哪个类加载器加载呢?

这就不得不提到「双亲委派机制」。

首先,我们需要知道的是,Java 语言系统中支持以下4种类加载器:

  • Bootstrap ClassLoader 启动类加载器
  • Extention ClassLoader 标准扩展类加载器
  • Application ClassLoader 应用类加载器
  • User ClassLoader 用户自定义类加载器

这四种类加载器之间,是存在着一种层次关系的,如下图
image.png

一般认为上一层加载器是下一层加载器的父加载器,那么,除了 BootstrapClassLoader 之外,所有的加载器都是有父加载器的。

那么,所谓的双亲委派机制,指的就是:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
**
那么,什么情况下父加载器会无法加载某一个类呢?

其实,Java中提供的这四种类型的加载器,是有各自的职责的:

  • Bootstrap ClassLoader ,主要负责加载 Java 核心类库,%JRE_HOME%\lib 下的rt.jar、resources.jar、charsets.jar 和 class 等。
  • Extention ClassLoader,主要负责加载目录 %JRE_HOME%\lib\ext 目录下的 jar 包和 class 文件。
  • Application ClassLoader ,主要负责加载当前应用的 classpath 下的所有类
  • User ClassLoader ,用户自定义的类加载器,可加载指定路径的 class 文件

那么也就是说,一个用户自定义的类,如 com.hollis.ClassHollis 是无论如何也不会被 Bootstrap 和Extention 加载器加载的。
**

为什么需要双亲委托

首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

另外,通过双亲委派的方式,还保证了安全性。因为 Bootstrap ClassLoader 在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如 java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的 JDK。这就可以避免有人自定义一个有破坏功能的 java.lang.Integer 被加载。这样可以有效的防止核心 Java API 被篡改。

父子加载器之间的关系是继承吗?

双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。
**
如下为ClassLoader中父加载器的定义:

  1. public abstract class ClassLoader {
  2. // The parent class loader for delegation
  3. private final ClassLoader parent;
  4. }

双亲委托是怎么实现的?

实现双亲委派的代码都集中在 **java.lang.ClassLoader****loadClass()** 方法之中

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. synchronized (getClassLoadingLock(name)) {
  5. // 首先检验 class 是否已加载
  6. Class<?> c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // class 找不到,抛出异常
  17. }
  18. if (c == null) {
  19. long t1 = System.nanoTime();
  20. c = findClass(name);
  21. PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  22. PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  23. PerfCounter.getFindClasses().increment();
  24. }
  25. }
  26. if (resolve) {
  27. resolveClass(c);
  28. }
  29. return c;
  30. }
  31. }

代码不难理解,主要就是以下几个步骤:

  1. 先检查类是否已经被加载过
  2. 若没有加载则调用父加载器的 loadClass() 方法进行加载
  3. 若父加载器为空则默认使用启动类加载器作为父加载器。
  4. 如果父类加载失败,抛出 ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。

如何主动破坏双亲委托机制

因为双亲委派过程都是在 loadClass 方法中实现的,那么 想要破坏这种机制,那么就自定义一个类加载器,重写其中的 **loadClass** 方法,使其不进行双亲委派即可。