帶你瞭解Java Agent

Java Agent這個技術,對於大多數同窗來講都比較陌生,可是多多少少又接觸過,實際上,咱們平時用的不少工具,都是基於Java Agent實現的,例如常見的熱部署JRebel,各類線上診斷工具(btrace, greys),還有阿里最近開源的arthas。java

其實Java Agent一點都不神祕,也是一個Jar包,只是啓動方式和普通Jar包有所不一樣,對於普通的Jar包,經過指定類的main函數進行啓動,可是Java Agent並不能單獨啓動,必須依附在一個Java應用程序運行,有點像寄生蟲的感受。api

如何動手寫一個Java Agent

由於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

前面說了,一個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啓動參數。

相關文章
相關標籤/搜索