QQ_79C089 发表于 2024-9-18 11:40

申请会员ID:xhmlwaf

1、申 请 I D:xhmlwaf
2、个人邮箱:331379635@qq.com
3、原创技术文章:java后端服务在线(不重启)用arthas工具修改代码

一.为什么要在线修改代码

一般在线修改代码是为了排查问题,而在不重启的情况下修改代码有他的必要性
1.重启会导致当前环境发生变化,导致问题无法复现。
2.用户正在使用服务,重启服务会导致用户体验不好。


二.Arthas在线修改代码的原理


我们知道,java在启动后,如果代码没有明确去动态加载类,一般是不会重新去加载的。
例如有以下代码可以加载一个类
String driverName = "com.mysql.jdbc.Driver";
Class clazz = Class.forName(driverName);
这个代码会加载指定的类:com.mysql.jdbc.Driver

那怎么在运行过程中重新加载修改后的类

1.这里用到了javaagent机制

Jdk5增加了一个包java.lang.instrument,提供了对Jvm底层组件的访问能力,Instrument要求在运行前利用命令行参数或者系统参数设置代{过}{滤}理类,VM启动完成之后(绝大多数类加载前)初始化。

开发基于instrument的应用,需要这么几个步骤:

步骤1:编写premain函数
步骤2:jar文件打包,制定Premain-Class
步骤3:使用-javaagent参数启动

Jdk6以后,针对这点进行了改进,开发者可以在main函数执行之后再启动自己的Instrument应用,入口是agentmain函数。arthas就是通过这个实现的。


Instrumentation 接口的关键代码
public interface Instrumentation {
    /**
   * 注册一个Transformer,从此之后的类加载都会被Transformer拦截。
   * Transformer可以直接对类的字节码byte[]进行修改
   */
    void addTransformer(ClassFileTransformer transformer);
   
    /**
   * 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
   * retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
   */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
   
    /**
   * 获取一个对象的大小
   */
    long getObjectSize(Object objectToSize);
   
    /**
   * 将一个jar加入到bootstrap classloader的 classpath里
   */
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
   
    /**
   * 获取当前被JVM加载的所有类对象
   */
    Class[] getAllLoadedClasses();
}

Arthas通过Instrumentation API注册一个ClassFileTransformer,这个Transformer可以在类加载到JVM时,对类的字节码进行修改。例如,Arthas可以在方法的开始和结束时插入自定义的监控代码,从而实现对方法执行时间的监控。



三.Arthas在线修改代码的流程


1.启动arthas并attach到目标jvm上


java -jararthas-boot.jar PID

这里的PID可以通过top命令查看


这里的原理不细说,有需要的自己去了解下,Attach API是Java 6引入的一个接口,允许一个Java进程动态附加到另一个运行中的Java进程。Arthas利用Attach API将自身的Java Agent动态附加到目标JVM上,进而实现对目标应用的监控。通过Attach API,Arthas可以在无需重启目标应用的情况下,动态加载和卸载自己的监控代码。


2.导出目标类代码
jad --source-only com.xxx.xxxx.KafkaDispositionConsumer > /tmp/java/KafkaDispositionConsumer.java

导出的是源码,.java格式,这意味着我们可以直接修改编辑它。

3.修改目标类代码
这里的修改按自己的需要修改,但是注意一下两点:
不能添加类变量(成员变量和静态变量都不行)
不能添加方法


可以做的就是在方法中间进行修改


4.重新编译修改后的代码
这里有条件的,可以在开发环境编译好class文件再拿过来使用。
如果没有条件,例如代码无法传出去,也可以使用arthas的编译功能(有几率失败)
mc -c 类加载器的ClassLoaderHash /tmp/java/KafkaDispositionConsumer.java

这里类加载器的ClassLoaderHash,可以通过以下命令获取
sc -d com.xxx.xxxx.KafkaDispositionConsumer   
我们需要使用这个类原有的加载器来编译修改的文件。

5.重新加载修改后的文件
上一步的过程会输出字节码文件路径
redefine 上一步输出的类路径。

通过以上操作后,就可以实现不重启的情况下修改线上代码。












Hmily 发表于 2024-9-23 17:12

抱歉,未能达到申请要求,申请不通过,可以关注论坛官方微信(吾爱破解论坛),等待开放注册通知。
页: [1]
查看完整版本: 申请会员ID:xhmlwaf