在 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
里的“先委派、后尝试自己加载”,简单理解就是,有一个事情到我手了,我先找父类做,找不到再自己干。
这种设计避免了多次加载同一个类,保证了核心类的唯一性和安全性。