Java热部署必须打破双亲委派,用可销毁的自定义类加载器隔离新旧类,通过文件监听触发重建加载器、defineClass加载新字节码,并清理旧实例强引用以促使其被GC回收。

Java 中热部署不能靠默认的双亲委派类加载器直接实现,必须打破双亲委派,用自定义类加载器隔离新旧版本的类,并配合资源监控与实例替换完成“热更新”。核心在于:类加载器需可销毁、类定义可重复生成、老实例要被安全替换。
自定义类加载器绕过双亲委派
标准 ClassLoader 会优先委托父加载器加载类,导致无法重新加载已加载的类。解决方法是重写 loadClass 方法,跳过父委托逻辑(仅对目标路径下的类),并使用 defineClass 直接解析字节码:
- 继承 URLClassLoader 或直接继承 ClassLoader
- 重写 loadClass(String name, boolean resolve),对关注的包(如 com.example.service)直接调用 findClass(name)
- 在 findClass 中读取最新 .class 文件或字节数组,调用 defineClass(name, bytes, 0, bytes.length)
- 不调用 super.loadClass,避免触发双亲委派
类隔离与实例生命周期管理
每次热更新需创建新类加载器实例,确保新旧类在 JVM 中属于不同运行时类(Class != Class 即使类名相同)。同时必须清理旧类的强引用:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 将业务对象(如 Service 实例)持有为 WeakReference 或托管到容器中统一管理
- 停止旧实例的后台线程、关闭连接、释放监听器等资源(提供 destroy() 接口)
- 显式将旧实例置为 null,促使其尽快被 GC(注意:静态字段、ThreadLocal、JNI 引用会阻碍卸载)
- 避免在新类中直接引用旧类的静态成员或单例
触发机制:文件监听 + 类重载
热部署需要感知代码变更并执行加载流程。可用 java.nio.file.WatchService 监控 class 文件或 jar 变更:
立即学习“Java免费学习笔记(深入)”;
- 启动独立线程监听 ENTRY_PATH/classes/ 或 target/classes/
- 捕获 ENTRY_MODIFY 事件后,校验时间戳或 MD5,确认非临时写入
- 新建一个自定义 ClassLoader(传入更新后的 URL),重新加载目标类
- 通过反射或工厂重建业务对象,并切换全局引用(如 Spring 中替换 BeanFactory 中的 singleton 对象)
注意事项与限制
Java 的类卸载条件苛刻,热部署在生产环境仍受限:
- JVM 不允许卸载系统类(java.*、javax.*)和 Bootstrap 加载的类
- 类加载器及其加载的所有类、其 Class 对象、所有实例、以及它们的静态变量,都必须无任何 GC Roots 引用
- 使用 JRebel 或 Spring Boot DevTools 是更稳妥的选择——它们在字节码层做增强,避免完全重启类加载器
- 涉及 JNI、动态代理(CGLIB)、Lambda 生成类的场景,热替换容易失败,需额外处理

















