热替换核心是替换承载变量行为的类定义而非变量本身,需重写findClass、新建ClassLoader、抽离状态至外部对象或接口抽象,并确保旧类资源可被GC回收。

在Web容器中用ClassLoader实现变量类热替换,核心不是“替换变量”,而是“替换承载变量行为的类定义”,同时保证运行中对象引用不中断。关键在于隔离类加载边界、规避双亲委派冲突、并通过接口或反射解耦类型强依赖。
必须绕开双亲委派的缓存陷阱
默认ClassLoader调用loadClass时会先委托父加载器,并缓存已加载类——哪怕class文件已更新,也不会重新读取字节码。所以热替换必须:
- 重写
findClass(而非loadClass),跳过父委托,直接从指定路径读取最新class文件字节流 - 每次热更新都新建一个ClassLoader实例,避免旧类残留于缓存中
- 确保被热替换的类不被系统类加载器(如AppClassLoader)提前加载,否则
defineClass会抛LinkageError: attempted duplicate class definition
变量状态如何延续?靠外部持有+接口抽象
类被替换后,原实例无法直接转型为新类实例(ClassCastException)。解决变量状态延续的常用方式是:
- 把可变数据抽离到独立对象(如
ConfigHolder、StateContext),由容器全局管理,热替换类只负责逻辑,不持有状态 - 热替换目标类实现统一接口(如
DataProcessor),所有调用方仅依赖该接口,新ClassLoader加载的新实现类仍可赋值给同一接口引用 - 若必须复用原实例字段,可通过反射读取老对象字段值,在新实例中手动重建(需字段名/类型兼容)
Web容器中需额外处理生命周期与资源泄漏
在Tomcat、Jetty等容器中,热替换不只是加载新类,还涉及上下文清理:
- 旧ClassLoader及其加载的所有类、静态字段、线程局部变量(ThreadLocal)必须能被GC回收——避免内存泄漏(常见于监听器、定时任务未注销)
- Servlet、Filter、Listener等容器托管组件,不能直接热替换;应将其逻辑下沉到可热加载的业务类中,由容器组件委托调用
- 建议配合文件监听(如
WatchService)检测WEB-INF/classes或指定目录下class/jar变更,触发ClassLoader重建与服务切换
一个轻量可行的结构示意
假设有一个PriceRuleEngine类频繁调整规则:
- 定义接口
PriceRule,所有规则实现类都实现它 - 容器启动时用
HotRuleClassLoader加载PriceRuleImpl_v1 - 当
PriceRuleImpl_v2.class写入监控目录,创建新HotRuleClassLoader,加载v2并实例化 - 将接口引用原子切换(如用
AtomicReference<pricerule></pricerule>),后续请求即走新逻辑 - v1类及其ClassLoader在无引用后可被回收


















