上一篇我们做了JVM类加载机制的零基础入门,搞懂了基础加载体系、双亲委派、沙箱机制和类与对象的关系。
今天我们直接进阶深挖 ,彻底吃透类加载的高阶核心能力。
一、快速梳理:JDK8 类加载机制
先做深度复盘,区别于入门篇,今天我们重点区分:类加载器层级体系 + 类加载器实现类细节,逐个拆解原理、特点、优缺点。
先用三句话总结JDK8的类加载机制:
-
类缓存:每个类加载器对他加载过的类都有一个缓存。
-
双亲委派:向上委托查找,向下委托加载。
-
沙箱保护机制:不允许应用程序加载JDK内部的系统类。
JDK8中加载体系分类以下两种:

左侧是JDK中实现的类加载器,通过parent属性形成父子关系。应用中自定义的类加载器的parent都是AppClassLoader
右侧是JDK中的类加载器实现类。通过类继承的机制形成体系。未来我们就可以通过继承相关的类实现自定义类加载器。
下面进行详细介绍
1.1 JDK8 四大类加载器(层级+实现类深度解析)
很多人分不清:加载器是什么、底层谁实现的、各自优缺点是什么,这里一次性讲透。
1、启动类加载器(Bootstrap ClassLoader)
实现原理 :由 C++ 语言编写,JVM 底层原生实现,无 Java 实例,在 Java 层获取结果为 null。
负责加载 :jre/lib 下核心 rt.jar、charsets.jar 等系统底层核心类。
特点:
-
权限最高、优先级最高
-
JVM 启动第一时间加载,速度最快
-
不继承 ClassLoader 父类,是独立底层实现
优缺点:
-
优点:底层稳定、速度快、安全性极高
-
缺点:完全封闭,开发者无法干预、无法扩展
2、扩展类加载器(Extension ClassLoader)
实现类 :sun.misc.Launcher$ExtClassLoader
原理:纯 Java 实现,继承自 ClassLoader,遵循双亲委派,父加载器为 Bootstrap。
负责加载:JDK 扩展目录下的拓展工具类、系统辅助Jar包。
特点:
-
层级第二层,承接系统核心与业务代码
-
只加载系统拓展类,不加载用户业务代码
优缺点:
-
优点:拓展系统能力、隔离系统与业务类
-
缺点:使用场景极少,日常开发几乎感知不到
3、应用类加载器(Application ClassLoader)
实现类 :sun.misc.Launcher$AppClassLoader
原理 :Java实现,双亲委派,父加载器为扩展类加载器,我们项目默认加载器。
负责加载:项目自定义类、Maven依赖Jar、classpath下所有业务代码。
特点:使用频率最高、承载所有业务代码加载。
优缺点:
-
优点:通用性强、适配所有常规业务项目、稳定无bug
-
缺点:灵活性差、不支持热加载、不支持多版本类共存、无法加载外部动态Jar
4、自定义类加载器(Custom ClassLoader)
实现原理 :开发者继承 ClassLoader 重写加载方法,自主控制加载逻辑。
特点:完全自定义、自由度最高。
优缺点:
-
优点:支持热部署、代码加密、动态加载Jar、多版本类共存
-
缺点:需要手动处理双亲委派、容易出现类冲突、实现有成本
JDK8中的类加载器都继承于一个统一的抽象类ClassLoader,类加载的核心也在这个父类中。其中,加载类的核心方法如下:
//类加载器的核心方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 每个类加载起对他加载过的类都有一个缓存,先去缓存中查看有没有加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
//没有加载过,就走双亲委派,找父类加载器进行加载。
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 父类加载起没有加载过,就自行解析class文件加载。
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment(); }
}
//这一段就是加载过程中的链接Linking部分,分为验证、准备,解析三个部分。
// 运行时加载类,默认是无法进行链接步骤的。
if (resolve) {
resolveClass(c);
}
return c;
}
}
这个方法就是最为核心的双亲委派机制。并且这个方法是protected声明的,这意味着,这个方法是可以被子类覆盖的。所以,双亲委派机制也是可以被打破的。
当一个类加载器要加载一个类时,整体的过程就是通过双亲委派机制向上委托查找,如果没有查找到,就向下委托加载。整个过程整理如下图:

1.2 沙箱保护机制(核心源码级理解)
双亲委派机制有一个最大的作用就是要保护JDK内部的核心类不会被应用覆盖。而为了保护JDK内部的核心类, JAVA在双亲委派的基础上,还加了一层保险。入门我们只懂概念,进阶我们直接看底层源码逻辑,搞懂沙箱到底怎么防篡改。
1.2.1 沙箱保护核心源码原理
沙箱安全本质:依靠双亲委派 + JVM 类加载校验,禁止用户篡改系统核心包类。
核心源码位于 ClassLoader.loadClass() 核心逻辑:
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// 不允许加载核心类
if ((name != null) && name.startsWith("java.")) { throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf( '. ')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
1、优先向上委派,让启动类加载器加载java.lang、java.util 等核心类;
2、如果用户自定义 java.lang.String,委派到顶层加载器后,系统已经存在该类;
3、JVM 校验发现:系统核心包不允许用户自定义加载,直接抛出安全异常,拒绝加载用户自定义类。
源码核心结论:
沙箱不是独立机制,是双亲委派 + 系统包校验共同实现的安全能力,杜绝恶意覆盖JDK核心类,保证底层运行安全。
二、Linking 类链接过程(完整阶段)
很多人只知道类加载分「加载、链接、初始化」,但完全不懂链接做了什么。
**链接(Linking)**是加载之后、初始化之前的核心步骤,分为三步:
2.1 验证(Verify)
校验 class 文件合法性:格式校验、字节码校验、权限校验、系统安全校验。
作用:防止恶意、损坏、不规范的字节码破坏JVM运行。
2.2 准备(Prepare)
给类的静态变量分配内存、赋默认初始值(int=0、boolean=false、引用=null)。
注意:这一步不会赋值自定义初始值,自定义赋值在初始化阶段执行。
2.3 解析(Resolve)
把代码中的符号引用 (字符串形式的类名、方法名),替换成直接内存地址引用。
如果A类中有一个静态属性,引用了另一个B类。那么在对类进行初始化的过程中,因为A和B这两个类都没有初始化,JVM并不知道A和B这两个类的具体地址。所以这时,在A类中,只能创建一个不知道具体地址的引用,指向B类。这个引用就称为符号引用 。而当A类和B类都完成初始化后, JVM自然就需要将这个符号引用转而指向B类具体的内存地址,这个引用就称为直接引用 。
通俗总结:
其中关于半初始化状态就是JDK在处理一个类的static静态属性时,会先给这个属性分配一个默认值,作用是占住内存。然后等连接过程完成后,在后面的初始化阶段,再将静态属性从默认值修改为指定的初始值。
三、类加载器加载外部Jar:优缺点与适用场景
常规项目只能加载classpath内的Jar,而自定义类加载器可以动态加载磁盘任意外部Jar包。
3.1 实现原理
通过 URLClassLoader 指定外部Jar路径,动态读取、加载外部class字节码,无需重启项目。
3.2 核心优点
-
无需重启服务,动态引入新Jar、新功能
-
实现插件化架构,功能可插拔
-
隔离外部Jar与项目内部类,避免版本冲突
3.3 缺点
-
容易出现类重复加载、内存泄漏
-
需要手动管理Jar资源释放
-
调试难度大,线上问题排查复杂
3.4 适用场景
-
插件化系统(IDE插件、后台功能插件)
-
动态扩展功能、无需重启更新
-
多版本Jar共存、解决依赖冲突
四、自定义类加载器实现Class代码混淆(加密与落地)
企业级常用防护手段:防止class反编译、防止源码泄露、保护核心业务代码。
4.1 Class文件加密常用方法
1、简单异或加密(最常用、轻量)
对class字节码数组进行固定密钥异或运算,修改原始字节码,普通反编译工具直接乱码,无法查看源码。
2、AES对称加密(高强度)
对class文件整体AES加密,安全性极高,适合核心机密业务代码。
3、字节码乱序、冗余插入(混淆加固)
打乱字节码指令、插入无效指令,增大反编译阅读难度。
4.2 自定义加载器解密执行原理
常规加载器读取加密class文件会报错,自定义加载器可以:
-
读取加密字节码
-
内存中实时解密还原正常class字节码
-
交给JVM正常加载运行
关键点 :磁盘上永远是加密文件,内存中临时解密,外部无法获取源码。
4.3 项目应用场景
-
商业项目、付费源码保护
-
核心算法、核心业务逻辑防泄露
-
私有化部署项目,防止客户反编译篡改代码
-
规则引擎、统一审批规则、订单状态规则等
五、自定义类加载器实现热加载
热加载 = 代码修改无需重启服务,实时生效,底层完全依赖自定义类加载器。
5.1 热加载核心原理
JVM 判定类唯一标准:全类名 + 加载器实例
同一个类名,更换新的自定义加载器实例加载,就会生成全新的Class对象,实现热替换。
5.2 热加载常用方案
1、自定义类加载器重载(原生方案)
监测class文件变动,销毁旧加载器、新建加载器重新加载类,轻量高效。
2、Spring Boot DevTools
底层就是自定义类加载器实现的快速重启、热加载能力。
3、JRebel 热加载插件
商用级热部署,基于类加载器动态替换,支持几乎所有代码热更新。
5.3 应用场景
-
开发环境提速,不用频繁重启项目
-
线上紧急BUG热修复、不停机更新
-
动态业务规则更新、配置类动态刷新
六、打破双亲委派:实现同类多版本共存
双亲委派是默认规则,但很多中间件、容器必须打破它。
6.1 打破双亲委派的核心方法
双亲委派的核心逻辑在 loadClass() 方法,想要打破,只需重写loadClass方法。
默认流程:先委派父加载器 → 父加载不到自己加载
打破后流程:优先自己加载,再委派父加载器,彻底反转加载顺序。
6.2 核心作用:同类多版本共存
不同自定义加载器,可以加载全类名完全一致、版本不同的类,实现多版本共存。
6.3 经典落地场景:Tomcat 类加载体系
类加载体系如下图:

tomcat的几个主要类加载器:
- commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本;
- Jsp类加载器:针对每个JSP页面创建一个加载器。这个加载器比较轻量级,所以Tomcat还实现了热加载,也就是JSP只要修改了,就创建一个新的加载器,从而实现了JSP页面的热更新。
Tomcat 是打破双亲委派的典型代表:
-
Tomcat 自定义 WebappClassLoader
-
优先加载当前项目Web应用的类,再委派父加载器
-
实现不同Web项目,依赖不同版本Jar、同名类互不冲突
通俗解释:一台Tomcat部署多个项目,A项目spring5、B项目spring6,互不冲突,就是靠打破双亲委派实现的。
6.4 适用场景总结
-
web容器多项目隔离
-
中间件多版本依赖兼容
-
插件化架构多版本共存
-
热部署、动态替换类场景
七、类加载器能不能不用反射?SPI扩展机制落地
很多人以为动态加载类必须用反射,其实 SPI 机制可以替代反射,实现无侵入扩展。
7.1 核心问题解答:类加载可以不用反射吗?
可以。
反射是「运行时主动获取类信息、调用方法」;
SPI 是基于类加载器的自动发现机制,无需手写反射代码。
7.2 SPI 扩展机制原理
SPI(服务提供者接口):JDK自带的服务发现机制。
核心逻辑:
-
定义统一接口规范
-
在固定配置文件中写入接口实现类全限定名
-
通过
ServiceLoader利用类加载器自动加载所有实现类
全程无需反射代码,自动实例化、自动发现扩展实现。
7.3 SPI 应用场景
-
JDBC 驱动自动加载(经典SPI落地)
-
框架扩展点、自定义插件实现
-
中间件扩展、策略模式自动注入
-
解耦接口与实现,无需硬编码反射
7.4 核心总结
类加载 + SPI = 优雅替代反射,实现解耦、可扩展、插件化架构,是各大框架底层核心设计思想。
八、全文串联总结
-
四大类加载器各有优劣,Bootstrap安全底层、AppClassLoader通用但死板、自定义加载器灵活强大。
-
沙箱机制基于双亲委派+系统校验,从源码层面杜绝核心类被篡改。
-
链接过程负责校验、静态内存分配、符号引用解析,是类初始化的前置关键步骤。
-
自定义加载器可动态加载外部Jar,适配插件化架构,但需注意内存泄漏。
-
Class加密混淆依靠自定义加载器内存解密,实现源码保护、防泄露。
-
热加载依靠更换类加载器实例,实现类动态替换、无需重启服务。
-
重写loadClass可打破双亲委派,支撑Tomcat多项目多版本类共存。
-
SPI机制依托类加载器实现服务自动发现,优雅规避反射硬编码,实现高度扩展。