热更实现
- 新建一个java项目,编写一个类ClassReloader.java实现
public static void premain(String agentArgs, Instrumentation inst)方法 - 打成jar,修改MANIFEST.MF
- 启动服务器的时候jvm参数要加一行,jar路径可以是相对路径
-javaagent:C:/jow/LoadTest/test.jar - 在自己需要热更功能的项目中新写一个类ClassReloadManager.java,新增一个方法
public static void reload(String path)传入需要热更的class路径,注意这个路径必须是类的全路径(包含包路径),调用ClassReloader.reload() - 写一个test试试
热更案例
- 新建空项目,编写一个类agent.ClassReloader.java.
package agent;
public class ClassReloader {
// 热更句柄
private static Instrumentation inst = null;
// 初始化
public static void premain(String agentArgs, Instrumentation inst) {
ClassReloader.inst = inst;
}
// 热更类
public static void reload(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException {
inst.redefineClasses(definitions);
}
}
导出jar包test.jar,然后修改jar包中的MANIFEST.MF
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.1
Premain-Class: agent.ClassReloader //指定全路径
Can-Redefine-Classes: true //指定为true,可以重新定义类
Created-By: 14.1-b02 (Sun Microsystems Inc.)
Boot-Class-Path: test.jar //指定jar包的路径,全路径或者相对路径都可以
- 新建一个空项目编写两个类ClassReloadManager.java和Test.java
package main;
import agent.ClassReloader;
public class ClassReloadManager {
public static void reload() throws ClassNotFoundException, IOException, UnmodifiableClassException {
reload("jowC");
}
public static void reload(String path) throws ClassNotFoundException, IOException, UnmodifiableClassException {
ClassReloader.reload(readAllClass(path));
}
/**
* 读取filePath目录下的所有class文件,并根据类的全路径读取class信息
*/
public static ClassDefinition[] readAllClass(String filePath) throws ClassNotFoundException, IOException {
// Log.game.info("start read Class,dir path= : " + filePath);
System.out.println("start read Class,dir path= : " + filePath);
List<File> files = new ArrayList<>();
readClassFiles(filePath, files);
ClassDefinition[] result = new ClassDefinition[files.size()];
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
String url = file.getAbsolutePath().replace("bin", "@");
url = url.substring(url.indexOf('@') + 1).replaceAll("\\\\", ".").replaceAll("/", ".");
String forName = url.substring(1, url.length() - 6);
// Log.game.info("read class:" + forName);
Class<?> clazz = Class.forName(forName);
result[i] = new ClassDefinition(clazz, readClassBuffer(file));
}
return result;
}
public static void readClassFiles(String path, List<File> result) {
File dir = new File(path);
if (!dir.exists() || !dir.isDirectory())
return;
File[] dirFiles = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() || file.getName().endsWith(".class");
}
});
for (File file : dirFiles) {
if (file.isDirectory()) {
readClassFiles(file.getAbsolutePath(), result);
} else {
result.add(file);
}
}
}
public static byte[] readClassBuffer(File classFile) throws IOException {
byte[] buffer = new byte[(int) classFile.length()];
FileInputStream fis = new FileInputStream(classFile);
BufferedInputStream bis = new BufferedInputStream(fis);
try {
bis.read(buffer);
} catch (IOException arg7) {
throw arg7;
} finally {
bis.close();
}
return buffer;
}
}
package main;
public class Test {
public static void main(String[] args) throws Exception {
talk();
ClassReloadManager.reload("C:/classhotfix");
talk();
}
public static void talk() {
// System.out.println("miao miao miao");
System.out.println("wang wang wang");
}
}
- 运行。编译两个Test,不同的是talk方法输出的内容不同,然后放到C:/classhotfix目录下最后路径应该是:classhotfix/main/Test.java。修改jvm启动参数
-javaagent:C:/jow/LoadTest/test.jar - 输出
wang wang wang
miao miao miao
- 最后
1.如果项目已经在运行中了,则ClassReloader应该实现public static void agentmain(String agentArgs, Instrumentation inst);方法。通过绑定pid来热更。
2.我经历过的项目里热更出现过宕机。尝试过jdk1.8.144 jdk1.8.152 jdk1.8.192。每个版本都说它修复了ciObjectFactory::create_new_metadata,但是每个版本都宕过机,每次宕机都去看官网,都说在之后的版本修复了……
https://bugs.openjdk.java.net/browse/JDK-8134389,然而没有。但是jdk9不敢用在线上项目,真的头疼。