1、申 请 I D:xhmlwaf
2、个人邮箱:331379635@qq.com
3、原创技术文章:java后端服务在线(不重启)用arthas工具修改代码
一.为什么要在线修改代码
一般在线修改代码是为了排查问题,而在不重启的情况下修改代码有他的必要性
1.重启会导致当前环境发生变化,导致问题无法复现。
2.用户正在使用服务,重启服务会导致用户体验不好。
二.Arthas在线修改代码的原理
我们知道,java在启动后,如果代码没有明确去动态加载类,一般是不会重新去加载的。
例如有以下代码可以加载一个类
[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 接口的关键代码
[Java] 纯文本查看 复制代码 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上
[Bash shell] 纯文本查看 复制代码 java -jar arthas-boot.jar PID
这里的PID可以通过top命令查看
这里的原理不细说,有需要的自己去了解下,Attach API是Java 6引入的一个接口,允许一个Java进程动态附加到另一个运行中的Java进程。Arthas利用Attach API将自身的Java Agent动态附加到目标JVM上,进而实现对目标应用的监控。通过Attach API,Arthas可以在无需重启目标应用的情况下,动态加载和卸载自己的监控代码。
2.导出目标类代码
[Bash shell] 纯文本查看 复制代码 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 上一步输出的类路径。
通过以上操作后,就可以实现不重启的情况下修改线上代码。
|