Java Agent(java 探針)雖然說在 jdk1.5 以後就有了,可是對於絕大多數的業務開發 javaer 來講,這個東西仍是比較神奇和陌生的;雖然說在實際的業務開發中,不多會涉及到 agent 開發,可是每一個 java 開發都用過,好比使用 idea 寫了個 HelloWorld.java,並運行一下, 仔細看控制檯輸出git
本篇將做爲 Java Agent 的入門篇,手把手教你開發一個統計方法耗時的 Java Agentgithub
首先明確咱們的開發環境,選擇 IDEA 做爲編輯器,maven 進行包管理apache
建立一個新的項目(or 子 module),而後咱們新建一個 SimpleAgent 類bash
public class SimpleAgent {
/** * jvm 參數形式啓動,運行此方法 * * @param agentArgs * @param inst */
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain");
}
/** * 動態 attach 方式啓動,運行此方法 * * @param agentArgs * @param inst */
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentmain");
}
}
複製代碼
咱們先忽略上面兩個方法的具體玩法,先簡單看一下這兩個方法的區別,註釋上也說了markdown
其中 jvm 方式,也就是說要使用這個 agent 的目標應用,在啓動的時候,須要指定 jvm 參數 -javaagent:xxx.jar
,當咱們提供的 agent 屬於基礎必備服務時,能夠用這種方式jvm
當目標應用程序啓動以後,並無添加-javaagent
加載咱們的 agent,依然但願目標程序使用咱們的 agent,這時候就可使用 attach 方式來使用(後面會介紹具體的使用姿式),天然而然的會想到若是咱們的 agent 用來 debug 定位問題,就能夠用這種方式socket
上面一個簡單 SimpleAgent 就把咱們的 Agent 的核心功能寫完了(就是這麼簡單),接下來須要打一個 Jar 包maven
經過 maven 插件,能夠比較簡單的輸出一個合規的 java agent 包,有兩種常見的使用姿式編輯器
在 pom.xml 文件中,添加以下配置,請注意一下manifestEntries
標籤內的參數
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>
<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
複製代碼
而後經過 mvn assembly:assembly
命令打包,在target
目錄下,能夠看到一個後綴爲jar-with-dependencies
的 jar 包,就是咱們的目標
經過配置文件MANIFEST.MF
,可能更加常見,這裏也簡單介紹下使用姿式
META-INF
META-INF
目錄下,新建文件MANIFEST.MF
文件內容以下
Manifest-Version: 1.0
Premain-Class: com.git.hui.agent.SimpleAgent
Agent-Class: com.git.hui.agent.SimpleAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
複製代碼
請注意,最後的一個空行(若是我上面沒有顯示的話,多半是 markdown 渲染有問題),不能少,在 idea 中,刪除最後一行時,會有錯誤提醒
而後咱們的pom.xml
配置,須要做出對應的修改
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
<!--<manifestEntries>-->
<!--<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>-->
<!--<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>-->
<!--<Can-Redefine-Classes>true</Can-Redefine-Classes>-->
<!--<Can-Retransform-Classes>true</Can-Retransform-Classes>-->
<!--</manifestEntries>-->
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
複製代碼
一樣經過mvn assembly:assembly
命令打包
agent 有了,接下來就是須要測試一下使用 agent 的使用了,上面提出了兩種方式,咱們下面分別進行說明
首先新建一個 demo 項目,寫一個簡單的測試類
public class BaseMain {
public int print(int i) {
System.out.println("i: " + i);
return i + 2;
}
public void run() {
int i = 1;
while (true) {
i = print(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
BaseMain main = new BaseMain();
main.run();
Thread.sleep(1000 * 60 * 60);
}
}
複製代碼
測試類中,有一個死循環,各 1s 調用一下 print 方法,IDEA 測試時,能夠直接在配置類,添加 jvm 參數,以下
請注意上面紅框的內容爲上一節打包的 agent 絕對地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
執行 main 方法以後,會看到控制檯輸出
請注意上面的premain
, 這個就是咱們上面的SimpleAgent
中的premain
方法輸出,且只輸出了一次
在使用 attach 方式時,能夠簡單的理解爲要將咱們的 agent 注入到目標的應用程序中,因此咱們須要本身起一個程序來完成這件事情
public class AttachMain {
public static void main(String[] args) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
// attach方法參數爲目標應用程序的進程號
VirtualMachine vm = VirtualMachine.attach("36633");
// 請用你本身的agent絕對地址,替換這個
vm.loadAgent("/Users/......./target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar");
}
}
複製代碼
上面的邏輯比較簡單,首先經過jps -l
獲取目標應用的進程號
當上面的 main 方法執行完畢以後,控制檯會輸出相似下面的兩行日誌,能夠簡單的理解爲我連上目標應用,並丟了一個 agent,而後揮一揮衣袖不帶走任何雲彩的離開了
Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:63710', transport: 'socket'
複製代碼
接下來再看一下上面的 BaseMain 的輸出,中間夾着一行agentmain
, 就代表 agent 被成功注入進去了
本文介紹了 maven + idea 環境下,手把手教你開發一個 hello world 版 JavaAgent 並打包的全過程
兩個方法
方法 | 說明 | 使用姿式 |
---|---|---|
premain() |
agent 以 jvm 方式加載時調用,即目標應用在啓動時,指定了 agent | -javaagent:xxx.jar |
agentmain() |
agent 以 attach 方式運行時調用,目標應用程序正常工做時使用 | VirtualMachine.attach(pid) 來指定目標進程號 vm.loadAgent("...jar") 加載 agent |
兩種打包姿式
打包爲可用的 java agent 時,須要注意配置參數,上面提供了兩種方式,一個是直接在pom.xml
中指定配置
<manifestEntries>
<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>
<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
複製代碼
另一個是在配置文件 META-INF/MANIFEST.MF
中寫好(須要注意最後一個空行不可或缺)
Manifest-Version: 1.0
Premain-Class: com.git.hui.agent.SimpleAgent
Agent-Class: com.git.hui.agent.SimpleAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
複製代碼
固然本篇內容看完以後,會發現對 java agent 的實際開發仍是不太清楚,難道 agent 就是在前面輸出一行hello world
就完事了麼,這和想象中的徹底不同啊
下一篇博文將手把手教你實現一個方法統計耗時的 java agent 包,將詳細說明利用接口Instrumentation
來實現字節碼修改,從而是實現功能加強
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
一灰灰 blog