Description
背景
尽管在生产环境热更新代码,并不是很好的行为,很可能导致:热更不规范,同事两行泪。
但很多时候我们的确希望能热更新代码,比如:
线上排查问题,找到修复思路了,但应用重启之后,环境现场就变了,难以复现。怎么验证修复方案?
又比如:
本地开发时,发现某个开源组件有bug,希望修改验证。如果是自己编译开源组件再发布,流程非常的长,还不一定能编译成功。有没有办法快速测试?
Arthas是阿里巴巴开源的Java应用诊断利器,深受开发者喜爱。
下面介绍利用Arthas 3.1.0版本的 jad
/mc
/redefine
一条龙来热更新代码。
- Arthas: https://github.com/alibaba/arthas
- jad命令:https://alibaba.github.io/arthas/jad.html
- mc命令:https://alibaba.github.io/arthas/mc.html
- redefine命令:https://alibaba.github.io/arthas/redefine.html
Arthas在线教程
下面通过Arthas在线教程演示热更新代码的过程。
在例子里,访问 curl http://localhost/user/0
,会返回500错误:
{
"timestamp": 1550223186170,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"message": "id < 1",
"path": "/user/0"
}
下面通过热更新代码,修改这个逻辑。
jad反编译代码
反编译UserController
,保存到 /tmp/UserController.java
文件里。
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
修改反编绎出来的代码
用文本编辑器修改/tmp/UserController.java
,把抛出异常改为正常返回:
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
sc查找加载UserController的ClassLoader
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
可以发现是spring boot的 LaunchedURLClassLoader@1be6f5c3
加载的。
mc内存编绎代码
保存好/tmp/UserController.java
之后,使用mc(Memory Compiler)命令来编译,并且通过-c
参数指定ClassLoader
:
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
redefine热更新代码
再使用redefine命令重新加载新编译好的UserController.class
:
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
检验热更新结果
再次访问 curl http://localhost/user/0
,会正常返回:
{
"id": 0,
"name": "name0"
}
总结
Arthas里 jad
/mc
/redefine
一条龙来线上热更新代码,非常强大,但也很危险,需要做好权限管理。
比如,线上应用启动帐号是 admin,当用户可以切换到admin,那么
- 用户可以修改,获取到应用的任意内存值(不管是否java应用)
- 用户可以attach jvm
- attach jvm之后,利用jvm本身的api可以redefine class
所以:
- 应用的安全主要靠用户权限本身的管理
- Arthas主要是让jvm redefine更容易了。用户也可以利用其它工具达到同样的效果
最后,Arthas提醒您: 诊断千万条,规范第一条,热更不规范,同事两行泪。
Activity
hengyunabc commentedon Feb 20, 2019
如果有一些类比较复杂,那么有可能
jad
命令反编绎失败。那么后面的mc
命令也会失败。这时,可以在本地修改代码,然后把
.class
文件上传到服务器上。有一些服务器的权限比较严格,不允许直接上传文件,那么可以用一些技巧来绕过。比如传用
base64
命令:.class
文件为base64,再保存为result.txt到服务器上,新建result.txt,复制本地的内容,粘贴再保存
把服务器上的 result.txt还原为
.class
CjqDy commentedon Feb 27, 2019
mc 内存编译的时候会根据类所在的包路径在服务器生成指定路径,是这样吧?
redefine也是根据这个路径同步更新包里的class文件的
hengyunabc commentedon Feb 27, 2019
mc 不指定输出目录,默认就是target进程的工作目录。可以用
pwd
命令来查看。VanXD commentedon Mar 1, 2019
本地修改较为复杂的情况, 检查class是否能热更新可在idea中按ctrl+shift+f9
xiexingguang commentedon Mar 1, 2019
nice
wanglei134 commentedon Mar 24, 2021
编译一个类 但是这个类import了其他jar包的类 会报错该怎么办呢
hengyunabc commentedon Mar 24, 2021
@wangdonghello 看wiki,本地编译上传
gaochengyidlmu commentedon Feb 24, 2023
@hengyunabc
现在遇到泛型擦除的问题,通过 jad 反编译得到的java文件中,原本是 List para1 的变量,变成了 ArrayList para1,之后 mc 时,就会报错
Memory compiler error, exception message: Compilation Error
message: incompatible types: java.lang.Object cannot be converted to XXX
这个有没有什么办法可以处理,我现在想通过 arthas 的这个功能,进行批量操作,所以本地编译class行不通。
Contentsearch commentedon Jul 7, 2023
执行jad 反编译文件返回的是null 什么情况
cwdhf commentedon Oct 11, 2023
写入指定文件不要用./ 如果指定目录直接使用绝对地址,如果不指定就不要填
MaidSG commentedon Aug 5, 2024