Java Agent 這個技術出如今 JDK1.5 以後,對於大多數人來講都比較陌生,可是多多少少又接觸過,實際上,咱們平時用的不少工具,都是基於 Java Agent 實現的,例如常見的熱部署 JRebel,各類線上診斷工具(Btrace, Greys),還有阿里開源的 Arthas。java
其實 Java Agent 一點都不神祕,也是一個 Jar 包,只是啓動方式和普通 Jar 包有所不一樣,對於普通的Jar包,經過指定類的 main 函數進行啓動,可是 Java Agent 並不能單獨啓動,必須依附在一個 Java 應用程序運行。apache
咱們可使用 Agent 技術構建一個獨立於應用程序的代理程序,用來協助監測、運行甚至替換其餘 JVM 上的程序,使用它能夠實現虛擬機級別的 AOP 功能。api
首先,咱們先來寫一段簡單的 Agent 程序:socket
public class AgentTest { /** * 以 vm 參數的方式載入,在 java 程序的 main 方法執行以前執行 * * @param agentArgs * @param inst Agent技術主要使用的 api,咱們可使用它來改變和從新定義類的行爲 */ public static void premain(String agentArgs, Instrumentation inst) { System.out.println("premain start"); System.out.println(agentArgs); } /** * 以 Attach 的方式載入,在 Java 程序啓動後執行 */ public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("agentmain start"); System.out.println(agentArgs); } }
由於 Java Agent 的特殊性,須要一些特殊的配置,例如指定 Agent 的啓動類等。這樣才能在加載 Java Agent 以後,找到並運行對應的 agentmain 或者 premain 方法。配置方式主要有兩種,一種是利用 maven-assembly-plugin 插件(推薦),一種是 MANIFEST.MF 文件。maven
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Premain-Class>org.agent.AgentTest</Premain-Class> <Agent-Class>org.agent.AgentTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin>
在 META-INF 目錄下建立 MANIFEST.MF 文件:函數
Manifest-Version: 1.0 Agent-Class: org.agent.AgentTest Premain-Class: org.agent.AgentTest Can-Redefine-Classes: true Can-Retransform-Classes: true
值得一提的是,即便新建了 MANIFEST.MF 文件,仍然須要配置 maven-assembly-plugin 信息,不然 MANIFEST.MF 信息會被 Maven 生成的信息覆蓋掉。工具
<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> </archive> </configuration> </plugin>
配置完上面的內容,運行 <b>mvn assembly:single</b> 打包屬於 Java Agent 的 jar 包。spa
Java Agent 程序寫好了,怎麼運行它呢?上面看到 Agent 程序分爲兩種,一種是 premain 函數,在主程序運行以前執行;一種是 agentmain 函數,在主程序運行以後執行。Java 加載這兩種 Agent 程序也有區別:插件
經過 JVM 參數 <b>-javaagent:**.jar[=test]</b> 啓動,其中 test 爲傳入 premain 的 agentArgs 的參數,程序啓動的時候,會優先加載 Java Agent,並執行其 premain 方法,這個時候,其實大部分的類都尚未被加載,這個時候能夠實現對新加載的類進行字節碼修改,可是若是 premain 方法執行失敗或拋出異常,那麼 JVM 會被停止,這是很致命的問題。命令行
程序啓動以後,經過某種特定的手段加載 Java Agent,這個特定的手段就是 VirtualMachine 的 attach api,這個 api 實際上是 JVM 進程之間的的溝通橋樑,底層經過socket 進行通訊,JVM A 能夠發送一些指令給JVM B,B 收到指令以後,能夠執行對應的邏輯,好比在命令行中常常使用的 jstack、jps 等,不少都是基於這種機制實現的。
VirtualMachine 的實現位於 tools.jar 中。
<dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>${java.home}/../lib/tools.jar</systemPath> </dependency>
由於是進程間通訊,因此使用 attach api 的也是一個獨立的Java進程,下面是一個簡單的實現:
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { VirtualMachine virtualMachine = null; try { // 1100 是進程號 virtualMachine = VirtualMachine.attach("1100"); // 第一個參數是 agent jar包路徑,第二個參數爲傳入 agentmain 的 args 參數 virtualMachine.loadAgent("D:\\concurrency-0.0.1-SNAPSHOT-jar-with-dependencies.jar", "test"); } finally { if (virtualMachine != null) { virtualMachine.detach(); } } }
<br> <br> 推薦閱讀: