热部署通过创建新类加载器隔离类空间,跳过双亲委派加载业务类,保留系统类委托父加载,确保新旧版本class对象互不兼容且可回收。

Java 热部署通过自定义类加载器实现,核心在于绕过默认双亲委派机制,让新版本的类能被重新加载,同时避免旧类实例残留和内存泄漏。关键不是“替换类”,而是“隔离类空间”——每次修改后用新类加载器加载新字节码,老对象逐步淘汰,新请求走新类。
打破双亲委派,实现类隔离
默认的 AppClassLoader 不允许重复加载同名类,且遵循双亲委派(先委托父加载器),无法热更。需继承 ClassLoader 并重写 loadClass(String name, boolean resolve),**跳过父加载器对业务类的委托**:
- 对
java.*、javax.*、sun.*等系统类,仍调用super.loadClass()委托给父加载器,保证稳定性 - 对自定义包(如
com.example.service.*),直接调用defineClass()加载本地字节码,不向上委托 - 确保每个新版本使用**全新类加载器实例**,不同版本类在 JVM 中属于不同 Class 对象,互不兼容
动态获取并加载新字节码
类加载器本身不负责发现变更,需配合文件监听或外部触发:
- 用
WatchService监控.class文件或源码编译输出目录(如target/classes) - 检测到变更后,读取新的
.class文件为 byte[],传入自定义加载器的defineClass() - 注意:不能直接修改正在运行的方法体(如用
Instrumentation.redefineClasses是另一套机制),这里走的是“新类加载器 + 新实例”的路径
安全卸载与引用清理
类加载器能否被 GC,取决于它加载的所有类对象、静态变量、线程栈等是否全部不可达:
- 避免在类中持有全局静态引用(如
static Map<string object></string>存活旧实例) - Web 场景下,需清理 ServletContext、Listener、Filter 等容器注册项;Spring 环境要手动关闭旧
ApplicationContext - 建议采用“灰度切换”:新请求路由到新加载器实例,老请求自然结束,等所有旧实例出作用域后,类加载器自动被回收
实际集成中的关键约束
纯自定义类加载器热部署在生产环境慎用,因有明显限制:
-
对象无法跨加载器转型:旧
UserServiceImpl实例不能强转为新加载器加载的同名类 - 序列化/反序列化失败:类名相同但
Class对象不同,serialVersionUID校验会失败 - 第三方库(如 Hibernate、Logback)内部缓存了 Class 引用,可能引发
LinkageError或行为异常 - 更适合开发期轻量热更(如 Spring Boot DevTools 底层就结合了自定义类加载器 + 重启策略)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











