arthas 线上更新代码的场景:线上代码bug 参数值不对,if判断,代码写错了,应用场景不对,导致代码报错出现问题,这个时候我们可以避免版本的发布就不走hostfix分支发布,应为自动化部署比较麻烦,需要jenkins打包推镜像,我们小问题可以通过arthas 命令去热更新代码。
主要是可以通过redefine ,retransform 俩个命令去更新代码,但是俩者的方式是不一样的
加载外部的.class
文件,redefine jvm 已加载的类。
常见问题
提示
推荐使用 retransform 命令
redefine 的 class 不能修改、添加、删除类的 field 和 method,包括方法参数、方法名称及返回值
如果 mc 失败,可以在本地开发环境编译好 class 文件,上传到目标系统,使用 redefine 热加载 class
目前 redefine 和 watch/trace/jad/tt 等命令冲突,以后重新实现 redefine 功能会解决此问题
注意
注意, redefine 后的原来的类不能恢复,redefine 有可能失败(比如增加了新的 field),参考 jdk 本身的文档。
提示
reset
命令对redefine
的类无效。如果想重置,需要redefine
原始的字节码。提示
redefine
命令和jad
/watch
/trace
/monitor
/tt
等命令会冲突。执行完redefine
之后,如果再执行上面提到的命令,则会把redefine
的字节码重置。 原因是 jdk 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。redefine 的限制
- 不允许新增加 field/method
- 正在跑的函数,没有退出不能生效,比如下面新增加的
System.out.println
,只有run()
函数里的会生效
public class MathGame { public static void main(String[] args) throws InterruptedException { MathGame game = new MathGame(); while (true) { game.run(); TimeUnit.SECONDS.sleep(1); // 这个不生效,因为代码一直跑在 while里 System.out.println("in loop"); } } public void run() throws InterruptedException { // 这个生效,因为run()函数每次都可以完整结束 System.out.println("call run()"); try { int number = random.nextInt(); List<Integer> primeFactors = primeFactors(number); print(number, primeFactors); } catch (Exception e) { System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage()); } } }
这是arthas官网的命令操作方式以及说明,安照步骤操作更新热代码部署,实际运行是不生效的。
1,jad编译代码到linux路径下面:
jad --source com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl > /opt/WechatAuthEventServiceImpl.java
2,mc命令把修改代码反编译到linux路径:
- mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /opt/WechatAuthEventServiceImpl.java -d /opt
-
- mc -c 3d646c37 /opt/WechatAuthEventServiceImpl.java -d /opt
-
- 这俩个等同于一个,一个更加classloader的id 编译
- sc -d *WechatAuthEventServiceImpl | grep classLoader
-
查看所有的类加载器,如果你是springboot项目默认类加载器就是: org.springframework.boot.loader.LaunchedURLClassLoader
但是编译报了错:
下面提示你去看日志
查找日志文件:find -name "文件名称"
报错有几个:
第一种:
no souch file dirctory 目录的原因:classlader为空,原因是因为arthas 使用的jdk版本对不上导致找不到类加载器的报错这个使用 :因为官网也说了这个问题:
redefine
命令和jad
/watch
/trace
/monitor
/tt
等命令会冲突。执行完redefine
之后,如果再执行上面提到的命令,则会把redefine
的字节码重置。 原因是 jdk 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。
容器里面是没有java环境的,通过k8s部署java服务,直接用服务器jdk去编译不推荐。
上面这个问题可以参考issues解决,换个jdk版本,用jdk路径启动arthas
第二种:
at com.taobao.arthas.compiler.DynamicCompiler.buildByteCodes(DynamicCompiler.java:132)
这是内置的字节码本身抛出的异常,这个目前没有定位到具体原因导致,不排除跟jdk版本有关系
- 2022-09-11 01:25:05 [arthas-NettyHttpTelnetBootstrap-3-1] INFO c.a.a.d.i.n.h.logging.LoggingHandler -[id: 0x49c29981, L:/127.0.0.1:3658] READ: [id: 0x1d249a1d, L:/127.0.0.1:3658 - R:/127.0.0.1:45640]
- 2022-09-11 01:25:05 [arthas-NettyHttpTelnetBootstrap-3-1] INFO c.a.a.d.i.n.h.logging.LoggingHandler -[id: 0x49c29981, L:/127.0.0.1:3658] READ COMPLETE
- 2022-09-11 01:25:05 [arthas-NettyHttpTelnetBootstrap-3-4] INFO c.t.a.core.shell.term.impl.Helper -Loaded arthas keymap file from com/taobao/arthas/core/shell/term/readline/inputrc
- 2022-09-11 01:26:15 [arthas-command-execute] WARN c.t.a.c.c.k.MemoryCompilerCommand -Memory compiler error
- com.taobao.arthas.compiler.DynamicCompilerException: Compilation Error
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 100 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 103 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 112 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 121 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 132 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 136 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 218 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 243 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 254 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 263 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 303 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 346 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 347 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 352 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 379 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 384 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 389 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 391 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 398 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 474 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 516 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 604 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 609 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 614 ,
- message: cannot find symbol
- symbol: variable log
- location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 616 ,
-
- at com.taobao.arthas.compiler.DynamicCompiler.buildByteCodes(DynamicCompiler.java:132)
- at com.taobao.arthas.core.command.klass100.MemoryCompilerCommand.process(MemoryCompilerCommand.java:136)
- at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
- at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
- at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
- at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
- at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
- at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
- at java.util.concurrent.FutureTask.run(FutureTask.java:266)
- at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
- at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
- at java.lang.Thread.run(Thread.java:748)
这里面其他的line这种报错原因是因为我代码有中文,直接通过mc命令编译是编译不过的,你要用idea编辑器去编译代码,然后通过redefine命令加载到 程序里面,但是这种方式会出现不生效的情况,建议使用retransform。
mc编译出来的代码是和idea是不一样的:
唯一的区别多了代码的注释行号:
/*110*/
idea编译的class文件:
俩者多少还是有点区别的,一个是执行加载过的class,一个是没有执行过的,自然有区别。
这是官网的使用方式:
redefine
提示
推荐使用 retransform 命令
提示
加载外部的
.class
文件,redefine jvm 已加载的类。参考:Instrumentation#redefineClasses在新窗口打开
#常见问题
提示
推荐使用 retransform 命令
redefine 的 class 不能修改、添加、删除类的 field 和 method,包括方法参数、方法名称及返回值
如果 mc 失败,可以在本地开发环境编译好 class 文件,上传到目标系统,使用 redefine 热加载 class
目前 redefine 和 watch/trace/jad/tt 等命令冲突,以后重新实现 redefine 功能会解决此问题
注意
注意, redefine 后的原来的类不能恢复,redefine 有可能失败(比如增加了新的 field),参考 jdk 本身的文档。
提示
reset
命令对redefine
的类无效。如果想重置,需要redefine
原始的字节码。提示
redefine
命令和jad
/watch
/trace
/monitor
/tt
等命令会冲突。执行完redefine
之后,如果再执行上面提到的命令,则会把redefine
的字节码重置。 原因是 jdk 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。#参数说明
参数名称 参数说明 [c:] ClassLoader 的 hashcode [classLoaderClass:]
指定执行表达式的 ClassLoader 的 class name #使用参考
redefine /tmp/Test.class redefine -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class redefine --classLoaderClass sun.misc.Launcher$AppClassLoader /tmp/Test.class /tmp/Test\$Inner.class#结合 jad/mc 命令使用
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java mc /tmp/UserController.java -d /tmp redefine /tmp/com/example/demo/arthas/user/UserController.class
- jad 命令反编译,然后可以用其它编译器,比如 vim 来修改源码
- mc 命令来内存编译修改过的代码
- 用 redefine 命令加载新的字节码
#上传 .class 文件到服务器的技巧
使用
mc
命令来编译jad
的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用base64
命令来绕过。
在本地先转换
.class
文件为 base64,再保存为 result.txt
base64 < Test.class > result.txt
到服务器上,新建并编辑
result.txt
,复制本地的内容,粘贴再保存把服务器上的
result.txt
还原为.class
base64 -d < result.txt > Test.class
用 md5 命令计算哈希值,校验是否一致
#redefine 的限制
- 不允许新增加 field/method
- 正在跑的函数,没有退出不能生效,比如下面新增加的
System.out.println
,只有run()
函数里的会生效
public class MathGame { public static void main(String[] args) throws InterruptedException { MathGame game = new MathGame(); while (true) { game.run(); TimeUnit.SECONDS.sleep(1); // 这个不生效,因为代码一直跑在 while里 System.out.println("in loop"); } } public void run() throws InterruptedException { // 这个生效,因为run()函数每次都可以完整结束 System.out.println("call run()"); try { int number = random.nextInt(); List<Integer> primeFactors = primeFactors(number); print(number, primeFactors); } catch (Exception e) { System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage()); } } }
我们实际操作,把编译好的代码上传到linux目录,然后通过 retransform 更新到类加载器当中
redefine /root/WechatAuthEventServiceImpl.class
如何验证代码是否生效?
我这里加了日志请求接口说明是可以了:
三,替换jar文件热更新代码,这种方式也可以,直接把jar文件解压,然后替换到指定目录重启jar包,但是这种方式是需要单机版本是需要停机更新的,集群也能实现热更新代码不需要重启服务,因为容器化部署是支持负载均衡的。
如果线上出了问题,如果小问题的情况可以使用arthas 工具去热更新判断,官网也说了方法没有结束的循环,或者没有结束的这种方法,没有返回值的这种,也可以理解为资源一直占用,没有释放的程序代码,也不能新加方法去更新因为不生效,所以慎用这个方式去更新代码,还是通过重新部署修改代码最稳定,也最安全。具体的原因更加服务器环境配置,java环境决定。
————没有与生俱来的天赋,都是后天的努力拼搏(我是小杨,谢谢你的关注和支持)