双亲委派模型通过“先委托父加载器、失败才自行加载”及缓存机制天然防止类重复加载:每个加载器优先向上委托,启动类加载器加载核心类(如String),子加载器不再尝试;且loadClass首步检查findLoadedClass缓存,确保同一加载器内同名类只加载一次。

Java 类加载机制通过双亲委派模型天然防止同一个类被多次重复加载,关键在于“类的唯一性由全限定名 + 类加载器共同决定”,而双亲委派确保了加载请求向上委托、优先由父加载器完成——只要父加载器能加载,子加载器就不会再尝试。
双亲委派机制是核心防线
每个类加载器在收到加载请求时,不会立即查找自己的路径,而是先委托父加载器。这个过程逐层向上,直到启动类加载器。只有当所有上级都明确无法加载时,当前加载器才自己尝试。
- 一旦某个类被某一个类加载器成功加载,它就会缓存在该加载器的内部结构中(如defineClass生成的Class对象);后续对该类的相同请求,会直接返回已加载的Class实例,不再重复加载
- 例如:java.lang.String 总是由启动类加载器加载;即使应用目录下放了一个伪造的String.class,应用程序类加载器也不会去加载它——因为启动类加载器已经响应了请求
- 这种“一次加载、全局可见(在该加载器及其子加载器范围内)”的设计,从源头避免了重复定义
类加载器自身维护加载缓存
ClassLoader抽象类内部通过一个ConcurrentHashMap缓存已加载的Class对象(JDK 8+ 中为classes字段),key是类名,value是Class实例。每次loadClass时都会先调用findLoadedClass检查缓存。
- 这个检查发生在委托父加载器之前,也发生在自己执行findClass之前
- 所以即使绕过双亲委派(比如重写loadClass但没调用super.loadClass),只要正确调用findLoadedClass,也能避免同一加载器内重复加载
- 但注意:不同类加载器之间缓存不共享——这也是为什么打破委派后容易出现LinkageError的根本原因
运行时验证与链接阶段兜底
即使两个类加载器各自加载了同名类,在链接阶段(尤其是解析和初始化)也会触发校验。JVM要求:若两个类参与同一符号引用解析(如继承、接口实现、方法调用),它们必须由同一个类加载器加载,否则抛出LinkageError。
- 这属于被动防护:不是阻止加载,而是阻止“错误共存”
- 常见于Web容器中同时存在servlet-api.jar的多个版本,或自定义ClassLoader未正确委派时
- 因此,依赖LinkageError来发现问题,不如靠双亲委派提前规避
开发中要避免主动破坏委派
很多重复加载问题其实源于人为绕过双亲委派,比如:
- 自定义ClassLoader重写了loadClass方法,却未调用super.loadClass,导致本该由父加载的类被子加载器重复加载
- 在Servlet或Filter中new URLClassLoader加载业务jar,且未设置parent为当前线程上下文类加载器(TCCL)
- 显式使用Class.forName(className, true, someClassLoader)指定了非标准加载器,而该加载器与调用方所处环境不一致
只要严格遵守双亲委派,绝大多数重复加载问题都不会发生。这不是靠事后检测,而是靠加载策略本身的设计保障。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











