热更新 Java 代码
假如我们原来的代码是这样的:
package com.example; import java.util.concurrent.TimeUnit; public class App { public static void main(String[] args) throws InterruptedException { while (true) { // 每两秒钟打印一条信息 TimeUnit.SECONDS.sleep(3); sayHi(); } } private static void sayHi() { // 需要修改的标识 boolean flag = true; if (flag) { System.out.println("Hello,Java."); } else { System.out.println("Hello,Java中文社群."); } } }
我们现在想要把 flag
变量改为 false
就可以这样来做:
- 使用 Arthas 的内存编译工具将新的 Java 代码编译为字节码;
- 使用 Arthas 的
redefine
命令实现热更新。
1.编译字节码
首先,我们需要将新的 Java 代码编译为字节码,我们可以通过 Arthas 提供的 mc
命令实现,mc
是 Memory Compiler(内存编译器)的缩写。
实现示例如下:
[arthas@3478]$ mc /Users/admin/Desktop/App.java -d /Users/admin/Desktop Memory compiler output: /Users/admin/Desktop/com/example/App.class Affect(row-cnt:1) cost in 390 ms.
其中 -d
表示编译文件的存放位置。
小贴士:我们也可以使用 javac App.java 生成的字节码,它与此步骤执行的结果相同。
2.执行热更新
有了字节码文件之后,我们就可以使用 redefine
命令来实现热更新了,实现示例如下:
[arthas@51787]$ redefine /Users/admin/Desktop/com/example/App.class redefine success, size: 1
从上述结果可以看出,热更新执行成功,此时我们去控制台查看执行结果,如下图所示:
这说明热更新执行确实成功了。
Arthas 热更新注意事项
使用热更新功能有一些条件限制,我们只能用它来修改方法内部的一些业务代码,如果我们出现了以下任意一种情况,那么热更新就会执行失败:
- 增加类属性(类字段);
- 增加或删除方法;
- 替换正在运行的方法。
最后一条我们需要单独说明一下,假如我们把上面的示例改为如下代码:
package com.example; import java.util.concurrent.TimeUnit; public class App { public static void main(String[] args) throws InterruptedException { while (true) { // 每两秒钟打印一条信息 TimeUnit.SECONDS.sleep(3); boolean flag = true; if (flag) { System.out.println("Hello,Java."); } else { System.out.println("Hello,Java中文社群."); } } } }
那么此时我们再进行热更新操作修改 flag
的值,那么就会执行失败,因为我们替换的是正在运行中的方法,而我们正常示例中的代码之所以能成功,是因为我们在 while
无线循环中调用了另一个方法,而那个方法是被间歇性使用的,因此可以替换成功。
总结
本文我们讲了 Arthas 的概念以及具体的使用流程,Arthas 其实就是一个普通的 Java 程序,我们可以使用 java -jar arthas-boot.jar
来启动它,然后再选择我们要操作的 Java 进程,这样就可以实现状态监控和其他操作。
文章的后半部分,我们介绍了 Arthas 的热更新功能,而热更新本质上只需要使用一个 redefine
命令来加载新的字节码文件就可以实现热更新了,但需要注意热更新不能替换正在运行的方法,它只能修改方法内部的业务代码,如果修改了类字段或者是更改了类方法,那么热更新就会执行失败。