开发者学堂课程【线上问题排查利器 Alibaba Arthas(上):class 和 classloader 相关命令:jad 和 mc】学习笔记,与课程紧密连接,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/746/detail/13196
class 和 classloader 相关命令:jad 和 mc
内容介绍
一、简介
二、redefine
三、总结
一、简介
class/classloader(类/类加载器)相关的第二组命令的三个命令。
redefine:把新生成的字节码文件在内存中执行。
重新定义,可以让反编译变成修改过的代码,让它重新在程序执行的时候起作用
二、redefine
1.redefine 作用
加载外部的 .class 文件,redefine 到 JVM 虚拟机里
该操作比较危险,所以应注意:
(1)redefine 后的原来的类不能恢复,redefine 有可能失败(不能新增新的成员变量和成员方法(不能加新的字段和新的方法),只能在现有的方法中修改代码,比如增加了新的field )
(2)reset 命令对 redefine 的类无效。如果想重置,需要 redefine 原始的字节码。
(3)redefine 命令和 jad (反编译)/ watch /trace /monitor/tt 等命令会冲突,执行完 redefine 之后,如果再执行上面提到的命令,则会把redefine的字节码重置,意味着修改后的无效,所以redefine命令应在jad(反编译)/watch/trace/monitor/tt 等命令之后执行。
注:watch /trace /monitor/tt 是未讲内容
2.redefine的限制
(1)不允许新增加 field/method
(2)正在跑的函数,没有退出(正在执行)不能生效,比如下面新增加的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( )") 生效,因为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();
}
}
}
3.案例:结合 jad/mc 命令使用
cls:清除屏幕
(1)将数学游戏使用 jad 反编译 deno.MathGame
代码为 jad --source-only demo .MathGame ,回车
此步骤反编译到屏幕中,但需要反编译到文件中.使用jad 反编译 deno.MathGame 输出到/root/MathGame. Java,反编译时通过 --source-only 可只显示源码。
代码为jad --source-only demo .MathGame > /root/MathGame. Java,回车
此时在屏幕上看不到结果
“>”为重定向,相当于把输出在默认的情况下,arthas 将源代码输出到屏幕上。但输出到屏幕上面不能进行编辑,所以“>”符号,将它重定向到另外的设备里。此时就可以把它输出的文件里,然后对它进行重新编写。
切换到 /root 目录下,输入 ls ,按回车键
可观察到多出 MathGame.Java 文件
在 vim 里对 MathGame.Java 文件进行编辑
代码为vim MathGame.Java ,按回车键
可看出反编译的和源码有区别,源码 while (true){},反编译的为 do{} while (true);
在反编译的 main() 代码中加入 System.out.printIn (“在 main 函数中循环体内”);
代码如下:Public static vold main(String[] args) throws InterruptedException{
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
System.out.printIn(“在 main 函数中循环体内”);
//上行为加入代码,正常来说不起作用,因为该函数是正在执行的函数
} while (true);
}
在反编译的run()代码中加入System.out.printIn(“-- 计算中的函数 --”);
代码如下:
public void run() throws InterruptedException {
try {
System.out.printIn(“ -- 计算中的函数 --”);
//观察是否能输出“ -- 计算中的函数 --”
int number = random.nextInt() / 10000;
Listc<Integer> primeFactors=this.primeFactors(nunber);
MathGame.print(number,primeFactors);
}
catch(Exception e) {
System.out.println(String.format("llegalArgumentCount:%3d,”, this.illegalArgumentCount) + e.getMessage());
}
}
存盘退出;输入 wq 命令,按回车键
(2)按上面的代码编辑完毕以后,使用 mc 内存中对新的代码编译
-d:指定编译到 /root 路径中
编译 MathGame.java 文件
代码为mc /root/MathGame.java -d /root ,回车
此时已经将其定义出,放到 demo/MathGame.Class 目录下
//代码应写全,必须要写绝对路径,否则会在 arthas 目录下找文件
在,则报错
(3)使用 redefine 命令加载新的字节码到内存里,使字节码起作用
demo/MathGame.Class :要加载的字节码文件
代码为redefine /root/ demo/MathGame.Class,回车
Redefine sucess 即为成功
发现在代码执行之前,结果上方实际上没有输出,直到“--计算中的函数--”此句话出现,此时是 redefine 后的代码起作用
以上讲的便是redefine的作用,三条命令讲述完毕。
便于在实际开发过程中被诊断的时候,在服务器实际运行过程中,想要对代码进行调整或者修改来诊断错误,就可以进行如上操作。
输入代码 vim MathGame.java ,回车
注意:发现在编辑的过程中,System.out.printIn (“在 main 函数中循环体内”); 并没有显示
三、总结
类相关的命令:说明
Jad:反编译字节码文件得到 java 的源代码
Mc:在内存中将源代码编译成字节码
Redefine:将字节码文件重新加载到内存中执行