用于处理线上的一些逻辑bug的利器,这样,就不会为了一些几行代码的错误导致的bug,需要重新发版本重启服务器,而严重影响在线活跃,收入了。
1.实现一个代理类:
package com.lingyu.common.hotcode;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
/**
* java 代码热更新
*/
public class HotCodeAgent {
private static final Logger logger = LogManager.getLogger(HotCodeAgent.class);
public static Instrumentation INST;
/** JVM 首先尝试在代理类上调用以下方法 */
public static void premain(String agentArgs, Instrumentation inst) {
INST = inst;
}
/**
* 重新定义可能会改变方法体,常量池和属性。重定义不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能在将来的版本中解除。
* 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。 如果此方法抛出异常,则不会重新定义任何类。
*/
public static void reload(Class<?> clazz, byte[] data) {
try {
ClassDefinition classDefinition = new ClassDefinition(clazz, data);
INST.redefineClasses(classDefinition);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
2.在game-common的build.xml里增加节点:
<manifest file="MANIFEST.MF">
<attribute name="Built-By" value="${user.name}" />
<attribute name="Built-Date" value="${buildTime}" />
<attribute name="Implementation-Version" value="${branch}-${svn}-${version}" />
<attribute name="Premain-Class" value="com.lingyu.common.hotcode.HotCodeAgent" />
<attribute name="Can-Redefine-Classes" value="true" />
</manifest>
3.在启动参数中加入参数 java -javaagent:lib/game-common.jar -server -Xms1024m -Xmx1024m -jar game.jar
游戏服务器启动的时候 会根据game-common.jar里的manifest.mf文件查找premain-class调用premain方法 项目保留Instrumentation的引用。
4. 通过后台上传class文件 更新到线上 建议 先在内网环境测试,一般只热更新逻辑代码。
class文件-->通过后台上传-->把class字节数据分发给线上,解析,重新加载类定义。(该画个图)
public void handleHotCode(String message) {
// TODO Auto-generated method stub
HotCodeInfo info = JSON.parseObject(message,HotCodeInfo.class);
Class<?> clazz = null;
try {
//完整的包名类路径直接获取到已经加载的类
if(StringUtils.isNotEmpty(info.getReplaceName())){
clazz = Class.forName(info.getReplaceName());
}
else{
//直接根据spring的接口获取对应的类
clazz = GameServerContext.getBean(info.getName()).getClass();
}
HotCodeAgent.reload(clazz,info.getData());
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
}
最近遇到个问题,有没有遇到过热加载时有内部类或者匿名内部类的类 ,加载的时候没法成功。抛出的异常为:
2018-12-19 10:36:16.000 ERROR [Message SubScribe Monitor][HotCodeAgent] - class redefinition failed: attempted to add a method
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at com.lingyu.common.hotcode.HotCodeAgent.reload(HotCodeAgent.java:53)
或者:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
同样的一份class文件,为什么会提示新增或者删除了方法呢?
然后我们开始对比 javap -p ooxx.class ,发现在底部多了这些方法,这些都是内部类访问外部类的变量或者方法时调用导致的。
static java.util.Map access$0(com.lingyu.game.service.role.RoleManager);
static org.apache.logging.log4j.Logger access$1();
只要规避掉这样的调用,就可以让含有内部类的类热加载。
同事还是很努力的!点个赞!