一、JavaAgent是什麼?
JDK5中只能通過命令列引數在啟動JVM時指定javaagent引數來設定代理類,而JDK6中已經不僅限於在啟動JVM時通過配置引數來設定代理類,JDK6中通過 Java Tool API 中的 attach 方式,我們也可以很方便地在執行過程中動態地設定載入代理類,以達到 instrumentation 的目的。 Instrumentation 的最大作用,就是類定義動態改變和操作。
二、JavaAgent能幹什麼?
javaagent的主要的功能如下:
- 可以在載入class檔案之前做攔截把位元組碼做修改
- 可以在執行期將已經載入的類的位元組碼做變更,但是這種情況下會有很多的限制
- 還有其他的一些小眾的功能
- 獲取所有已經被載入過的類
- 獲取所有已經被初始化過了的類(執行過了clinit方法,是上面的一個子集)
- 獲取某個物件的大小
- 將某個jar加入到bootstrapclasspath裡作為高優先順序被bootstrapClassloader載入
- 將某個jar加入到classpath裡供AppClassloard去載入
- 設定某些native方法的字首,主要在查詢native方法的時候做規則匹配
想象一下可以讓程式按照我們預期的邏輯去執行,聽起來是不是挺酷的。
三、一個簡單的 Agent 實現
下面將通過一個具體的例子,來闡述如何開發一個簡單的 Agent 。這個 Agent 是通過 C++ 編寫的(讀者可以在最後下載到完整的程式碼),他通過監聽 JVMTI_EVENT_METHOD_ENTRY 事件,註冊對應的回撥函式來響應這個事件,來輸出所有被呼叫函式名。有興趣的讀者還可以參照這個基本流程,通過 JVMTI 提供的豐富的函式來進行擴充套件和定製。
Agent 的設計
具體實現都在 MethodTraceAgent 這個類裡提供。按照順序,他會處理環境初始化、引數解析、註冊功能、註冊事件響應,每個功能都被抽象在一個具體的函式裡。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class MethodTraceAgent { public: void Init(JavaVM *vm) const throw(AgentException); void ParseOptions(const char* str) const throw(AgentException); void AddCapability() const throw(AgentException); void RegisterEvent() const throw(AgentException); ... private: ... static jvmtiEnv * m_jvmti; static char* m_filter; }; |
Agent_OnLoad 函式會在 Agent 被載入的時候建立這個類,並依次呼叫上述各個方法,從而實現這個 Agent 的功能。
1
2
3
4
5
6
7
8
9
10
|
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { ... MethodTraceAgent* agent = new MethodTraceAgent(); agent->Init(vm); agent->ParseOptions(options); agent->AddCapability(); agent->RegisterEvent(); ... } |
執行過程如圖 1 所示:
圖 1. Agent 時序圖
Agent 編譯和執行
Agent 的編譯非常簡單,他和編譯普通的動態連結庫沒有本質區別,只是需要將 JDK 提供的一些標頭檔案包含進來。
- Windows:
12
cl /EHsc -I${JAVA_HOME}include -I${JAVA_HOME}includewin32
-LD MethodTraceAgent.cpp Main.cpp -FeAgent.dll
- Linux:
12
g++ -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/linux
MethodTraceAgent.cpp Main.cpp -fPIC -shared -o libagent.so
在附帶的程式碼檔案裡提供了一個可執行的 Java 類,預設情況下執行的結果如下圖所示:
圖 2. 預設執行輸出
現在,我們執行程式前告訴 Java 先載入編譯出來的 Agent:
1
|
java -agentlib:Agent=first MethodTraceTest |
這次的輸出如圖 3. 所示:
圖 3. 新增 Agent 後輸出
可以當程式執行到到 MethodTraceTest 的 first 方法是,Agent 會輸出這個事件。“ first ”是 Agent 執行的引數,如果不指定話,所有的進入方法的觸發的事件都會被輸出,如果讀者把這個引數去掉再執行的話,會發現在執行 main 函式前,已經有非常基本的類庫函式被呼叫了。
四、總結
Java 虛擬機器通過 JVMTI 提供了一整套函式來幫助使用者檢測管理虛擬機器執行態,它主要通過 Agent 的方式實現與使用者的互操作。通過 Agent 這種方式不僅僅使用者可以使用,事實上,JDK 裡面的很多工具,比如 Instrumentation 和 JDI, 都採用了這種方式。這種方式無需把這些工具繫結在虛擬機器上,減少了虛擬機器的負荷和記憶體佔用。
下載資源
- 本文用到的 C++ 和 Java 原始碼 (source.zip | 10 KB)