Java Agent這個技術,對於大多數同窗來講都比較陌生,可是多多少少又接觸過,實際上,咱們平時用的不少工具,都是基於Java Agent實現的,例如常見的熱部署JRebel,各類線上診斷工具(btrace, greys),還有阿里最近開源的arthas。java
其實Java Agent一點都不神祕,也是一個Jar包,只是啓動方式和普通Jar包有所不一樣,對於普通的Jar包,經過指定類的main函數進行啓動,可是Java Agent並不能單獨啓動,必須依附在一個Java應用程序運行,有點像寄生蟲的感受。api
由於Java Agent的特殊性,須要一些特殊的配置,在META-INF目錄下建立MANIFEST文件.socket
並在MANIFEST文件中指定Agent的啓動類maven
這裏須要解釋下爲何要指定 Agent-Class
和 Premain-Class
,在加載Java Agent以後,會找到 Agent-Class
或者 Premain-Class
指定的類,並運行對應的 agentmain
或者 premain
方法。函數
/** * 以vm參數的方式載入,在Java程序的main方法執行以前執行 */ public static void premain(String agentArgs, Instrumentation inst); /** * 以Attach的方式載入,在Java程序啓動後執行 */ public static void agentmain(String agentArgs, Instrumentation inst);
若是不想手動建立MANIFEST文件,也能夠經過Maven配置,在打包的時候自動生成,具體配置能夠參數下面。工具
<plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifestEntries> <Premain-Class>com.dianping.rhino.agent.AgentBoot</Premain-Class> <Agent-Class>com.dianping.rhino.agent.AgentBoot</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin>
因此,咱們須要在 agentmain
或者 premain
方法中實現具體的Agent邏輯,這裏是你大顯身手的地方,讀取JVM的各類數據,修改類的字節碼,只要你能想到的,通常均可以實現。spa
前面說了,一個Java Agent既能夠在程序運行前加載,也能夠在程序運行後加載,二者有什麼區別呢?命令行
經過JVM參數 -javaagent:**.jar
啓動,程序啓動的時候,會優先加載Java Agent,並執行其 premain
方法,這個時候,其實大部分的類都尚未被加載,這個時候能夠實現對新加載的類進行字節碼修改,可是若是 premain
方法執行失敗或拋出異常,那麼JVM會被停止,這是很致命的問題。code
程序啓動以後,經過某種特定的手段加載Java Agent,這個特定的手段就是 VirtualMachine
的 attach api
,這個api實際上是JVM進程之間的的溝通橋樑,底層經過socket進行通訊,JVM A能夠發送一些指令給JVM B,B收到指令以後,能夠執行對應的邏輯,好比在命令行中常常使用的jstack、jcmd、jps等,不少都是基於這種機制實現的。orm
由於是進程間通訊,因此使用 attach api
的也是一個獨立的Java進程,下面是一個簡單的實現。
// 15186表示目標進程的PID VirtualMachine vm = VirtualMachine.attach("15186"); try { // 指定Java Agent的jar包路徑 vm.loadAgent(".../agent.jar"); } finally { vm.detach(); }
首先,咱們得知道目標進程的PID,這個能夠經過jps指令方便獲得,也能夠經過 VirtualMachine
的list方法拿到本機全部Java進程的PID。經過 attach
鏈接上目標PID以後,能夠得到表示目標進程的vm對象,執行 loadAgent
方法,對應的Java Agent會被加載,而後會找到指定的入口類,並執行agentmain方法,若是執行出現普通異常(除了oom和其它致命異常),目標JVM並不會受到影響。
經過這種方式,能夠實現動態的加載Java Agent,而不須要修改JVM啓動參數。