热更新框架设计
目前设计只适用于当前系统,且使用框架需要二次开发。
需求简单分析
目前java热更代码的途经有很多,脚本如lua、Python等,Java Agent,自定义ClassLoader,每种方式都各有优缺点:
- 各种脚本:脚本语言分两类,基于JVM实现的Groovy、Jython等,这些脚本的热加载实现也是基于自定义ClassLoader;另一种像lua这种纯解释性语言,想要实现热更,只能每次调用都重新load脚本文件执行,或者缓存一份文件,再定义监听器监听文件变化后重新load缓存,实现起来跟自定义ClassLoader类似。
- Java Agent:Instrument方式可以直接修改运行时的class结构,但主要用途是修改方法体,不支持修改方法参数、返回值等信息,也不可以随意增删方法;如果是对原有算法进行修改,很难避免对方法返回值、参数等内容的修改,所以该实现方式不做优先考虑
- ClassLoader:优点明显,可以全部替换所有代码;缺点也明显,有内存泄露风险,每次热更需要保证之前所有引用都被清除,因为卸载class依赖GC,所以就要求所有自定义ClassLoader加载的class能够在新的classLoader加载完成后,将所有对象实例引用清空,保证原来class没有实例对象。
本次需求是通用热更框架,对于热更内容不确定,更新粒度不确定的内容,目前可以选择的只有ClassLoader。
考虑
- 使用ClassLoader存在内存泄露风险,所有ClassLoader加载出来的Class和这些Class的实例对象必须统一管理,方便后续更新时统一卸载
- ~~ClassLoader加载获得是Class,所以获取实例时需要调用class对象的newInstance方法,多少会影响性能,所有需要提供机制保证对象实例复用,~~暂时不考虑性能损耗
- 热更代码最好不在
AppClassLoader
中加载,这样可以不用打破双亲委派 - 安全点,目前设计暂时不考虑安全点,通过tick机制,每帧检测是否存在加载成功的Class实例,存在则直接替换之后再执行正常逻辑
限制
- 每个tick单元下支持一个热更jar包,如
MatchService
下可以支持一个热更jar包,利用MatchService
的tick驱动ReloadModule
去实现热更 - 热更的代码不可以保留跨Service引用,比如
MatchService
中热更出来的类不要在其他Service中保存引用 - 所有需要热更的代码只能存在于单独拉出来的项目中,不能在基础jar包中包含
- 热更Jar包中必须存在启动类,且需要在
MANIFEST.MF
文件中配置权限类名
方案
以Jar为单位更新,将需要更新的代码单独划分一个项目,单独打包;Jar包中需要有一个初始化类Starter提供初始化和卸载方法,当需要热更时加载新的Starter并进行初始化,完成替换之后调用之前Starter的卸载方法,删除各种引用。
本次设计没有具体到具体模块,每个需要热更的具体模块都需要在这个设计的基础上进行二次开发,包括IJarStarter
实现类、热更代码拆解项目、原项目缓存修改等。
类图: