类加载器有三层,按顺序往上托事:启动类加载器(Bootstrap)管JDK核心类如String,用C++实现、Java中为null;扩展/平台类加载器管lib/ext或模块化非核心类;应用程序类加载器(AppClassLoader)管classpath下用户代码;设计目的是通过双亲委派保障安全与类唯一性,必要时可打破委派实现热部署或JDBC驱动加载。

直接说“类加载器有三层,按顺序往上托事”最易懂。重点不是背名字,而是讲清谁管什么、为什么这么设计。
先说清楚三个角色各自管哪块
你可以把它们想象成公司里的三级审批:
-
启动类加载器(Bootstrap):最高领导,只管JDK最核心的类,比如
StringObjectArrayList这些。它用C++写的,Java里拿不到对象,打印出来是null——不是它没干活,是它太底层,不露面。 -
平台类加载器(JDK9+)或扩展类加载器(旧版):中层主管,负责JDK自带但非核心的模块,比如XML处理、加密工具等,路径在
java.base之外的其他系统模块,或旧版的lib/ext目录。 -
应用程序类加载器(AppClassLoader):一线员工,专干你写的代码和引入的jar包,也就是
classpath或-cp指定的那些东西。你运行java Main时,默认就用它。
委派过程就像“请示汇报”
不是谁拿到请求就自己干,而是先向上级请示:
- 你写了
new OrderService(),JVM让AppClassLoader去加载; - AppClassLoader不急着翻自己目录,先问平台类加载器:“你见过这个类吗?”;
- 平台类加载器也先问启动类加载器:“这玩意儿是不是核心类?”;
- 启动类加载器查一圈,发现
com.example.OrderService不在rt.jar或java.base里,回一句“没这人”; - 平台类加载器也查无此货,再往下传话;
- 最后AppClassLoader才打开自己的classpath,找到
OrderService.class,读字节码、生成Class对象。
为什么要这样绕一圈?两个硬道理
一是安全:确保java.lang.String永远由启动类加载器加载,别人没法用自定义类偷偷替换它;
二是唯一性:避免同一个类被不同加载器重复加载,造成类型不兼容——比如两个MyUtils.class哪怕字节码一模一样,如果由不同加载器加载,JVM就认为它们是两个不同的类,不能互相赋值。
打破委派的情况其实很常见
机制是默认规则,不是铁律。遇到以下场景就得反着来:
-
JDBC驱动加载:
DriverManager是启动类加载器加载的,但它要加载你放在lib下的mysql-connector.jar,就得靠线程上下文类加载器(TCCL)把请求“递下来”; - Tomcat热部署:每个Web应用需要独立的类空间,所以用自定义加载器,且不完全委派给父加载器,优先自己加载,避免应用之间类冲突;
- OSGi或模块化框架:需要精细控制类可见范围,必须绕过双亲委派,实现“父不加载、子先试”或“并行加载”策略。


















