• arthas 线上更新代码不生效的问题Memory compiler error, exception message: Compilation Error


    arthas 线上更新代码的场景:线上代码bug 参数值不对,if判断,代码写错了,应用场景不对,导致代码报错出现问题,这个时候我们可以避免版本的发布就不走hostfix分支发布,应为自动化部署比较麻烦,需要jenkins打包推镜像,我们小问题可以通过arthas 命令去热更新代码。

    主要是可以通过redefine ,retransform 俩个命令去更新代码,但是俩者的方式是不一样的

    redefine:

    加载外部的.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()函数里的会生效

    1. public class MathGame {
    2. public static void main(String[] args) throws InterruptedException {
    3. MathGame game = new MathGame();
    4. while (true) {
    5. game.run();
    6. TimeUnit.SECONDS.sleep(1);
    7. // 这个不生效,因为代码一直跑在 while里
    8. System.out.println("in loop");
    9. }
    10. }
    11. public void run() throws InterruptedException {
    12. // 这个生效,因为run()函数每次都可以完整结束
    13. System.out.println("call run()");
    14. try {
    15. int number = random.nextInt();
    16. List<Integer> primeFactors = primeFactors(number);
    17. print(number, primeFactors);
    18. } catch (Exception e) {
    19. System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
    20. }
    21. }
    22. }

    这是arthas官网的命令操作方式以及说明,安照步骤操作更新热代码部署,实际运行是不生效的。

    1,jad编译代码到linux路径下面:

    jad --source com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl > /opt/WechatAuthEventServiceImpl.java

    2,mc命令把修改代码反编译到linux路径:

    1. mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /opt/WechatAuthEventServiceImpl.java -d /opt
    2. mc -c 3d646c37 /opt/WechatAuthEventServiceImpl.java -d /opt
    3. 这俩个等同于一个,一个更加classloader的id 编译
    1. 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

    mc 命令一直报错Memory compiler error, exception message: null, please check $HOME/logs/arthas/arthas.log for more details. · Issue #799 · alibaba/arthas · GitHub

    第二种: 

        at com.taobao.arthas.compiler.DynamicCompiler.buildByteCodes(DynamicCompiler.java:132)
    这是内置的字节码本身抛出的异常,这个目前没有定位到具体原因导致,不排除跟jdk版本有关系

    1. 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]
    2. 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
    3. 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
    4. 2022-09-11 01:26:15 [arthas-command-execute] WARN c.t.a.c.c.k.MemoryCompilerCommand -Memory compiler error
    5. com.taobao.arthas.compiler.DynamicCompilerException: Compilation Error
    6. message: cannot find symbol
    7. symbol: variable log
    8. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 100 ,
    9. message: cannot find symbol
    10. symbol: variable log
    11. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 103 ,
    12. message: cannot find symbol
    13. symbol: variable log
    14. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 112 ,
    15. message: cannot find symbol
    16. symbol: variable log
    17. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 121 ,
    18. message: cannot find symbol
    19. symbol: variable log
    20. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 132 ,
    21. message: cannot find symbol
    22. symbol: variable log
    23. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 136 ,
    24. message: cannot find symbol
    25. symbol: variable log
    26. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 218 ,
    27. message: cannot find symbol
    28. symbol: variable log
    29. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 243 ,
    30. message: cannot find symbol
    31. symbol: variable log
    32. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 254 ,
    33. message: cannot find symbol
    34. symbol: variable log
    35. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 263 ,
    36. message: cannot find symbol
    37. symbol: variable log
    38. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 303 ,
    39. message: cannot find symbol
    40. symbol: variable log
    41. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 346 ,
    42. message: cannot find symbol
    43. symbol: variable log
    44. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 347 ,
    45. message: cannot find symbol
    46. symbol: variable log
    47. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 352 ,
    48. message: cannot find symbol
    49. symbol: variable log
    50. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 379 ,
    51. message: cannot find symbol
    52. symbol: variable log
    53. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 384 ,
    54. message: cannot find symbol
    55. symbol: variable log
    56. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 389 ,
    57. message: cannot find symbol
    58. symbol: variable log
    59. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 391 ,
    60. message: cannot find symbol
    61. symbol: variable log
    62. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 398 ,
    63. message: cannot find symbol
    64. symbol: variable log
    65. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 474 ,
    66. message: cannot find symbol
    67. symbol: variable log
    68. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 516 ,
    69. message: cannot find symbol
    70. symbol: variable log
    71. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 604 ,
    72. message: cannot find symbol
    73. symbol: variable log
    74. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 609 ,
    75. message: cannot find symbol
    76. symbol: variable log
    77. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 614 ,
    78. message: cannot find symbol
    79. symbol: variable log
    80. location: class com.volvo.im.chatservice.provider.service.impl.WechatAuthEventServiceImpl , line: 616 ,
    81. at com.taobao.arthas.compiler.DynamicCompiler.buildByteCodes(DynamicCompiler.java:132)
    82. at com.taobao.arthas.core.command.klass100.MemoryCompilerCommand.process(MemoryCompilerCommand.java:136)
    83. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
    84. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
    85. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
    86. at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
    87. at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
    88. at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    89. at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    90. at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    91. at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    92. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    93. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    94. at java.lang.Thread.run(Thread.java:748)

     这里面其他的line这种报错原因是因为我代码有中文,直接通过mc命令编译是编译不过的,你要用idea编辑器去编译代码,然后通过redefine命令加载到 程序里面,但是这种方式会出现不生效的情况,建议使用retransform。

    mc编译出来的代码是和idea是不一样的:

     唯一的区别多了代码的注释行号:

    /*110*/ 

    idea编译的class文件:

     俩者多少还是有点区别的,一个是执行加载过的class,一个是没有执行过的,自然有区别。

    二,retransform 方式

    这是官网的使用方式:

     

    redefine

    提示

    推荐使用 retransform 命令

    mc-redefine在线教程在新窗口打开

    提示

    加载外部的.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

    #使用参考

    1. redefine /tmp/Test.class
    2. redefine -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
    3. redefine --classLoaderClass sun.misc.Launcher$AppClassLoader /tmp/Test.class /tmp/Test\$Inner.class

    #结合 jad/mc 命令使用

    1. jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
    2. mc /tmp/UserController.java -d /tmp
    3. redefine /tmp/com/example/demo/arthas/user/UserController.class
    • jad 命令反编译,然后可以用其它编译器,比如 vim 来修改源码
    • mc 命令来内存编译修改过的代码
    • 用 redefine 命令加载新的字节码

    #上传 .class 文件到服务器的技巧

    使用mc命令来编译jad的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用base64命令来绕过。

    1. 在本地先转换.class文件为 base64,再保存为 result.txt

      base64 < Test.class > result.txt
      
    2. 到服务器上,新建并编辑result.txt,复制本地的内容,粘贴再保存

    3. 把服务器上的 result.txt还原为.class

      base64 -d < result.txt > Test.class
      
    4. 用 md5 命令计算哈希值,校验是否一致

    #redefine 的限制

    • 不允许新增加 field/method
    • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效

    1. public class MathGame {
    2. public static void main(String[] args) throws InterruptedException {
    3. MathGame game = new MathGame();
    4. while (true) {
    5. game.run();
    6. TimeUnit.SECONDS.sleep(1);
    7. // 这个不生效,因为代码一直跑在 while里
    8. System.out.println("in loop");
    9. }
    10. }
    11. public void run() throws InterruptedException {
    12. // 这个生效,因为run()函数每次都可以完整结束
    13. System.out.println("call run()");
    14. try {
    15. int number = random.nextInt();
    16. List<Integer> primeFactors = primeFactors(number);
    17. print(number, primeFactors);
    18. } catch (Exception e) {
    19. System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
    20. }
    21. }
    22. }

    我们实际操作,把编译好的代码上传到linux目录,然后通过 retransform 更新到类加载器当中

    redefine /root/WechatAuthEventServiceImpl.class

    如何验证代码是否生效? 

    •  1,命令行验证是否生效:retransform -l
    • 2,反编译代码。(这里更新了在反编译一次看看是否生效)
    • 3,请求接口

      我这里加了日志请求接口说明是可以了:

     

     三,替换jar文件热更新代码,这种方式也可以,直接把jar文件解压,然后替换到指定目录重启jar包,但是这种方式是需要单机版本是需要停机更新的,集群也能实现热更新代码不需要重启服务,因为容器化部署是支持负载均衡的。

    总结:

    如果线上出了问题,如果小问题的情况可以使用arthas 工具去热更新判断,官网也说了方法没有结束的循环,或者没有结束的这种方法,没有返回值的这种,也可以理解为资源一直占用,没有释放的程序代码,也不能新加方法去更新因为不生效,所以慎用这个方式去更新代码,还是通过重新部署修改代码最稳定,也最安全。具体的原因更加服务器环境配置,java环境决定。

    ————没有与生俱来的天赋,都是后天的努力拼搏(我是小杨,谢谢你的关注和支持)

     

  • 相关阅读:
    全网最全谷粒商城记录_07、环境-虚拟机网络设置
    小满Vue3第四十四章(Vue3 性能优化)
    解决jupyter找不到虚拟环境的问题
    实例分割Yolact边缘端部署 (二) 训练自己的模型-> onnx
    3DTiles 1.0 数据规范详解[4.1] b3dm瓦片二进制数据文件结构
    vue3热更新后导致组件执行两次的bug
    达芬奇调色:色彩理论入门
    聊聊Spring Cloud Gateway 动态路由及通过Apollo的实现
    qnx shell sh ,linux shell bash
    JTS: 23 lineDissolver 线段分割
  • 原文地址:https://blog.csdn.net/qq_39751120/article/details/126806373