Arthas是阿里巴巴開源的Java應用診斷利器,本文介紹Arthas 3.1.1版本里jad
命令的實現原理。
https://github.com/alibaba/arthas
jad命令介紹
https://alibaba.github.io/arthas/jad.html
jad即java decompiler,把JVM已加載類的字節碼反編譯成Java代碼。比如反編譯String
類:
$ jad java.lang.String
ClassLoader:
Location:
/*
* Decompiled with CFR .
*/
package java.lang;
import java.io.ObjectStreamField;
...
public final class String
implements Serializable,
Comparable<String>,
CharSequence {
private final char[] value;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
public String(byte[] arrby, int n, int n2) {
String.checkBounds(arrby, n, n2);
this.value = StringCoding.decode(arrby, n, n2);
}
...
獲取到類的字節碼
反編譯有兩部分工作:
獲取到字節碼
反編譯爲Java代碼
那麼怎麼從運行的JVM裏獲取到字節碼?
最常見的思路是,在classpaths
下面查找,比如 ClassLoader.getResource("java/lang/String.class")
,但是這樣子查找到的字節碼不一定對。比如可能有多個衝突的jar,或者有Java Agent修改了字節碼。
ClassFileTransformer機制
從JDK 1.5起,有一套ClassFileTransformer
的機制,Java Agent通過Instrumentation
註冊ClassFileTransformer
,那麼在類加載或者retransform
時就可以回調修改字節碼。
顯然,在Arthas裏,要增強的類是已經被加載的,所以它們的字節碼都是在retransform
時被修改的。
通過顯式調用Instrumentation.retransformClasses(Class<?>...)
可以觸發回調。
Arthas裏增強字節碼的watch
/trace
/stack
/tt
等命令都是通過ClassFileTransformer
來實現的。
ClassFileTransformer
的接口如下:
public interface ClassFileTransformer {
byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
看到這裏,讀者應該猜到jad
是怎麼獲取到字節碼的了:
註冊一個
ClassFileTransformer
通過
Instrumentation.retransformClasses
觸發回調在回調的
transform
函數裏獲取到字節碼刪掉註冊的
ClassFileTransformer
使用cfr來反編譯
獲取到字節碼之後,怎樣轉換爲Java代碼呢?
以前大家使用比較多的反編譯軟件可能是jd-gui
,但是它不支持JDK8的lambda語法和一些新版本JDK的特性。
後面比較成熟的反編譯軟件是cfr
,它以前是不開源的。直到最近的0.145
版本,作者終於開源了,可喜可賀。地址是
https://github.com/leibnitz27/cfr
在Arthas jad
命令裏,通過調用cfr
來完成反編譯。
jad命令的缺陷
99%的情況下,jad
命令dump下來的字節碼是準確的,除了一些極端情況。
因爲JVM裏註冊的
ClassFileTransformer
可能有多個,那麼在JVM裏運行的字節碼裏,可能是被多個ClassFileTransformer
處理過的。觸發了
retransformClasses
之後,這些註冊的ClassFileTransformer
會被依次回,上一個處理的字節碼傳遞到下一個。
所以不能保證這些ClassFileTransformer
第二次執行會返回同樣的結果。有可能一些
ClassFileTransformer
會被刪掉,觸發retransformClasses
之後,之前的一些修改就會丟失掉。
所以目前在Arthas裏,如果開兩個窗口,一個窗口執行watch
/tt
等命令,另一個窗口對這個類執行jad
,那麼可以觀察到watch
/tt
停止了輸出,實際上是因爲字節碼在觸發了retransformClasses
之後,watch
/tt
所做的修改丟失了。
精確獲取JVM內運行的java字節碼的辦法
如果想精確獲取到JVM內運行的Java字節碼,可以使用這個dumpclass
工具,它是通過sa-jdi.jar
來實現的,保證dump下來的字節碼是JVM內所運行的。
https://github.com/hengyunabc/dumpclass
總結
總結jad
命令的工作原理:
通過註冊
ClassFileTransformer
,再觸發retransformClasses
來獲取字節碼通過
cfr
來反編譯ClassFileTransformer
的方式來獲取字節碼有一定缺陷通過
dumpclass
工具可以精確獲取字節碼
jad
命令可以在線上快速檢查運行時的代碼,並且結合mc
/redefine
命令可以熱更新代碼:
鏈接
Arthas開源:https://github.com/alibaba/arthas
dumpclass: https://github.com/hengyunabc/dumpclass
https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html