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

2023-11-20

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()函数里的会生效

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

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版本有关系

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,一个是没有执行过的,自然有区别。

二,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

#使用参考

   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命令来绕过。

  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()函数里的会生效

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

如何验证代码是否生效? 

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

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

 

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

总结:

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

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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐