大白話JavaAgent

AskHarries發表於2019-03-03

一、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 編譯和執行

Agent 的編譯非常簡單,他和編譯普通的動態連結庫沒有本質區別,只是需要將 JDK 提供的一些標頭檔案包含進來。

  • Windows:
    1
    2
    cl /EHsc -I${JAVA_HOME}include -I${JAVA_HOME}includewin32
    -LD MethodTraceAgent.cpp Main.cpp -FeAgent.dll
  • Linux:
    1
    2
    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 後輸出

新增Agent後輸出

可以當程式執行到到 MethodTraceTest 的 first 方法是,Agent 會輸出這個事件。“ first ”是 Agent 執行的引數,如果不指定話,所有的進入方法的觸發的事件都會被輸出,如果讀者把這個引數去掉再執行的話,會發現在執行 main 函式前,已經有非常基本的類庫函式被呼叫了。

四、總結

Java 虛擬機器通過 JVMTI 提供了一整套函式來幫助使用者檢測管理虛擬機器執行態,它主要通過 Agent 的方式實現與使用者的互操作。通過 Agent 這種方式不僅僅使用者可以使用,事實上,JDK 裡面的很多工具,比如 Instrumentation 和 JDI, 都採用了這種方式。這種方式無需把這些工具繫結在虛擬機器上,減少了虛擬機器的負荷和記憶體佔用。

下載資源

大白話JavaAgent

相關文章