JAVA

双亲委派:JVM 里那个“先问爸爸”的类加载套路

转载:双亲委派:JVM 里那个“先问爸爸”的类加载套路

在 JVM 的类加载体系中,双亲委派机制是一个非常重要的设计思想,也是保障 Java 程序安全性和稳定性的重要基础。当我们在程序中请求某个类加载器去加载一个类时,它并不会立刻自己去找这个类,而是先把请求交给自己的父加载器,让父加载器去尝试加载。只有当父加载器无法找到这个类时,当前加载器才会尝试自己去加载

 

认识双亲委派


在认识之前必须先了解一下,类加载的种类:

  • 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等。
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。 
  • 应用类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写好的那些类。
  • 自定义加载器:负责加载用户自定义路径下的类包。

而双亲委派即当某个类加载器接到加载类的请求时,它首先不会自己处理,而是先把请求委托给它的父类加载器, 父加载器也采用同样的策略,继续把请求向上委托,一直到最顶层的 Bootstrap 类加载器, Bootstrap 类加载器负责加载 JDK 核心类库(rt.jar 中的类)。如果它能找到目标类,就返回这个类的定义, 如果父加载器都找不到目标类,才会由当前类加载器去查找和加载。

当加载某个字节码的时候,向上委托,遇到一个类,先由父级加载,一直加载到顶层加载器,然后再往回走看子类是否能加载。

 

双亲委派作用


沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改

避免类的重复加载:当父类已经加载了该类时,就没有必要让子类再去加载一遍,保证被加载类的唯一性。

全盘委托机制:当加载一个类的时候,会使用一个类加载器把类引用的类都加载,除非显示使用其他类加载器加载加载引用的类。

缺点:不利于隔离不同应用中的同名类, 更新类需要重启 JVM,因为父加载器已经缓存, 热更新、动态扩展等不方便 ,当然我们也可以去打破该机制,来实现自己的逻辑。

 

源码解析


在 JVM 中,双亲委派机制是由 ClassLoader 抽象类实现的,核心逻辑在它的 loadClass 方法。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查类是否已经被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                     // 2. 委派给父加载器
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 顶层 Bootstrap 加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父加载器没找到
            }
            if (c == null) {
                long t1 = System.nanoTime();
                // 4. 父加载器没找到,当前加载器自己加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

 

打破双亲委派


打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,当然一般来说通常推荐重写 findClass() 而不是 loadClass(),但是如果打破双亲委派还是需要重写 loadClass()方法。

loadClass():是 JVM 对外暴露的「加载类」入口方法,它的内部实现了双亲委派逻辑。

findClass():是子类重写实现自己的“寻找类”逻辑”的钩子方法,它只负责自己找如何加载,不管父类逻辑,不实现委派。

loadClass 实现委派逻辑,保证安全性,如果单纯想自己去定义自己加载类的方法就实现 findClass 即可,这样不会破坏 JVM 的整体类加载逻辑,也不会意外覆盖核心类或破坏隔离。

像在某些特殊场景下,如热加载,Tomcat 的实现逻辑已经 SPI 加载等,这个时候就需要先加载自己的类而不是去找父类。

 

总结


通过源码可以看出,双亲委派模型其实就是在 loadClass 里的“先委派、后尝试自己加载”,简单理解就是,有一个事情到我手了,我先找父类做,找不到再自己干。

这种设计避免了多次加载同一个类,保证了核心类的唯一性和安全性。