1.启动待测试的应用程序
[doda@host166 game]$ java -jar math-game.jar
2.替换操作步骤
2.1启动Arthas
[doda@host166 arthas]$ java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.6.1
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 316194 org.elasticsearch.bootstrap.Elasticsearch
[2]: 105913 smart-spark-1.0.jar
[3]: 164109 math-game.jar
3
[INFO] arthas home: /home/doda/shangeshishi/arthas
[INFO] Try to attach process 164109
[INFO] Attach process 164109 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.6.1
main_class
pid 164109
time 2022-05-15 15:33:34
2.2使用redefine命令替换的类进行反编译替换操作(不可还原)
#jad反编译出java文件,只显示源码,最后是文件反编译之后的存放路径
[arthas@151998]$ jad --source-only demo.MathGame > /home/doda/shangeshishi/arthas/game/MathGame.java
#修改反编译出来的java文件
[doda@host166 game]$ vi MathGame.java
#sc全称-search class,-d表示detail,获取类加载器classLoaderClass的hashCode编码
[arthas@164109]$ sc -d *MathGame | grep classLoader
classLoaderHash 42a57993
#用指定的classloader重新将类在内存中编译,生成.class文件到本地
[arthas@164109]$ mc -c 42a57993 /home/doda/shangeshishi/arthas/game/MathGame.java -d /home/doda/shangeshishi/arthas/game
Memory compiler output:
/home/doda/shangeshishi/arthas/game/demo/MathGame.class
Affect(row-cnt:1) cost in 970 ms.
#用redefine命令将本地编译后的.class类加载到JVM
[arthas@164109]$ redefine /home/doda/shangeshishi/arthas/game/demo/MathGame.class
redefine success, size: 1, classes:
demo.MathGame
替换class前后日志对比,代码添加的:System.out.println("出异常信息了!!!");已经马止打印出来,可见替换class已经成功了!
注意:如果mc编译中报下面错误,即有可能是使用openJdk,此jdk中缺少tool工具包,请临时引入完整jdk,用当前引入的jdk,然后重启应用和Arthas,即可解决:
Memory compiler error, exception message: Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(), please confirm the application running in JDK not JRE., please check $HOME/logs/arthas/arthas.log for more details.
[doda@host166 arthas]$ export JAVA_HOME=/home/doda/shangeshishi/jdk/jdk1.8.0_131
[doda@host166 arthas]$ export PATH=$JAVA_HOME/bin:$PATH
[doda@host166 arthas]$ export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
[doda@host166 arthas]$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
2.3使用retransform实现redefine替换(可还原)
2.3.1替换操作
[doda@host166 arthas]$ java -jar arthas-boot.jar
[arthas@164109]$ sc -d *MathGame | grep classLoader
classLoaderHash 42a57993
[arthas@164109]$ mc -c 42a57993 /home/doda/shangeshishi/arthas/game/MathGame.java -d /home/doda/shangeshishi/arthas/game
Memory compiler output:
/home/doda/shangeshishi/arthas/game/demo/MathGame.class
Affect(row-cnt:1) cost in 310 ms.
[arthas@164109]$ retransform /home/doda/shangeshishi/arthas/game/demo/MathGame.class
retransform success, size: 1, classes:
demo.MathGame
修改替换后的日志如下:
2.3.2retransform常用操作命令
查看 retransform entry,替换多少次显示多少次
[arthas@164109]$ retransform -l
Id ClassName TransformCount LoaderHash LoaderClassName
1 demo.MathGame 1 null null
2 demo.MathGame 1 null null
删除指定id的 retransform entry
[arthas@164109]$ retransform -d 1
[arthas@164109]$ retransform -l
Id ClassName TransformCount LoaderHash LoaderClassName
2 demo.MathGame 1 null null
删除所有 retransform entry
[arthas@164109]$ retransform --deleteAll
2.3.3消除 retransform 的影响,即还原替换前的class(使用redefine不可还原)
删除这个类对应的 retransform entry
重新触发retransform
对于同一个类,当存在多个 retransform entry时,如果显式触发 retransform ,则最后添加的entry生效(id最大的)
[arthas@164109]$ retransform --deleteAll
[arthas@164109]$ retransform --classPattern demo.MathGame
删除后,显式触发 retransform,可以还原原来的class,显示日志如下:
3.redefine和retransform替换class限制
都不允许新增加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());
}
}