线上排查问题神器Arthas(四)-不发版实现线上修改代码并生效

3,678 阅读2分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

线上代码修改

生产环境有时会遇到非常紧急的问题,或突然发现一个bug,这时候不方便重新发版,或者发版未生效,可以使用Arthas临时修改线上代码。通过Arthas修改的步骤如下:

1. 从读取.class文件
2. 编译成.java文件
3. 修改.java文件
4. 将修改后的.java文件编译成新的.class文件
5. 将新的.class文件通过classloader加载进JVM内

第一步:读取.class文件

sc -d *DeadLockTest*

使用sc命令查看JVM已加载的类信息。关于sc命令,查看官方文档:arthas.aliyun.com/doc/sc.html

  • -d : 表示打印类的详细信息

image

最后一个参数classLoaderHash,表示在jvm中类加载的hash值,我们要获得的就是这个值。

第二步:使用jad命令将.class文件反编译为.java文件才行

jad -c 7c53a9eb --source-only com.lxl.jvm.DeadLockTest > /Users/lxl/Downloads/DeadLockTest.java
  • jad命令是反编译指定已加载类的源码
  • -c : 类所属 ClassLoader 的 hashcode
  • --source-only:默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。
  • com.lxl.jvm.DeadLockTest:目标类的全路径
  • /Users/lxl/Downloads/DeadLockTest.java:反编译文件的保存路径
       /*
        * Decompiled with CFR.
        * 
        * Could not load the following classes:
        *  com.lxl.jvm.User
        */
       package com.lxl.jvm;
       
       import com.lxl.jvm.User;
       import java.util.ArrayList;
       import java.util.List;
       
       public class DeadLockTest {
           private static Object lock1 = new Object();
           private static Object lock2 = new Object();
           private static List<String> names = new ArrayList<String>();
           private List<String> citys = new ArrayList<String>();
       
           public static List<String> getCitys() {
               DeadLockTest deadLockTest = new DeadLockTest();
/*25*/         deadLockTest.citys.add("北京");
/*27*/         return deadLockTest.citys;
           }
       
          ......
       
           public static void main(String[] args) {
              ......
           }
       }

这里截取了部分代码。

第三步:修改java文件

          public static List<String> getCitys() {
               System.out.println("-----这里增加了一句日志打印-----");
               DeadLockTest deadLockTest = new DeadLockTest();
/*25*/         deadLockTest.citys.add("北京");
/*27*/         return deadLockTest.citys;
           }

第四步:使用mc命令将.java文件编译成.class文件

mc -c 512ddf17 -d /Users/luoxiaoli/Downloads /Users/luoxiaoli/Downloads/DeadLockTest.java
  • mc: 编译.java文件生.class文件, 详细使用方法参考官方文档arthas.aliyun.com/doc/mc.html
  • -c:指定classloader的hash值
  • -d:指定输出目录
  • 最后一个参数是java文件路径

这是反编译后的class字节码文件

image

第五步:使用redefine命令,将.class文件重新加载进JVM

redefine -c 512ddf17 /Users/lxl/Downloads/com/lxl/jvm/DeadLockTest.class
  • -c: 指定ClassLoader的hashcode

image

最后看到redefine success,表示重新加载.class文件进JVM成功了。

注意事项

redefine命令使用之后,再使用jad命令会使字节码重置,恢复为未修改之前的样子。官方关于redefine命令的说明

第六步:检验效果

这里检测效果,调用接口,执行日志即可。