Java Agent -- Instrumentation

1 Instrumentation簡介

使用 Instrumentation,開發者能夠構建一個獨立於應用程序的代理程序(Agent),用來監測和協助運行在 JVM 上的程序,甚至可以替換和修改某些類的定義。有了這樣的功能,開發者就能夠實現更爲靈活的運行時虛擬機監控和 Java 類操做了,這樣的特性實際上提供了一種虛擬機級別支持的 AOP 實現方式,使得開發者無需對 JDK 作任何升級和改動,就能夠實現某些 AOP 的功能了。java

Instrumentation是從Java 5後存在的重要特性,Java 6又對其多了一系列優化。詳見 http://www.ibm.com/developerworks/cn/java/j-lo-jse6/git

在 Java SE6 裏面,最大的改變使運行時的 Instrumentation 成爲可能。apache

在 Java SE 5 中,Instrument 要求在運行前利用命令行參數或者系統參數來設置代理類,在實際的運行之中,虛擬機在初始化之時(在絕大多數的 Java 類庫被載入以前),instrumentation 的設置已經啓動,並在虛擬機中設置了回調函數,檢測特定類的加載狀況,並完成實際工做。可是在實際的不少的狀況下,咱們沒有辦法在虛擬機啓動之時就爲其設定代理,這樣實際上限制了 instrument 的應用。數組

Java SE 6 的新特性改變了這種狀況,經過 Java Tool API 中的 attach 方式,咱們能夠很方便地在運行過程當中動態地設置加載代理類,以達到 instrumentation 的目的。jvm

在實際中,這兩種方式如今都被普遍使用甚至同時存在於一個項目中。下面咱們就來具體看看這兩種方式的具體作法。maven

2 Instrumentation的基本用法

2.1 Premain方式

Premain即爲Java 5中的在運行前設置代理類的方式。函數

咱們能夠在一個普通 Java 程序(帶有 main 函數的 Java 類)運行時,經過 – javaagent參數指定一個特定的 jar 文件(包含 Instrumentation 代理)來啓動 Instrumentation 的代理程序。工具

簡單步驟

步驟一:建立Premain類,編寫premain函數

編寫一個 Java 類,包含以下兩個方法當中的任何一個優化

代碼塊ui

Plain Text

public static void premain(String agentArgs, Instrumentation inst); //優先級更高,若二者都存在,只會執行該方法
public static void premain(String agentArgs);
  • agentArgs 是 premain 函數獲得的程序參數,隨同 「– javaagent」一塊兒傳入。與 main 函數不一樣的是,這個參數是一個字符串而不是一個字符串數組,若是程序參數有多個,程序將自行解析這個字符串。

  • Inst 是一個 java.lang.instrument.Instrumentation 的實例,由 JVM 自動傳入。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個接口,也是這個包的核心部分,集中了其中幾乎全部的功能方法,例如類定義的轉換和操做等等

在這個 premain 函數中,開發者能夠進行對類的各類操做。

步驟二:添加manifest

在src/main/resources/META-INF中建立MANIFEST.MF文件,屬性當中加入」 Premain-Class」來指定步驟 1 當中編寫的那個帶有 premain 的 Java 類。

代碼塊

Plain Text

Manifest-Version: 1.0
Premain-Class: Premain

步驟三:打jar包

若是是用maven管理的工程,打包前能夠加入maven-jar-plugin插件,以便用-jar來執行。而且能夠控制相應的manifest文件和main函數

代碼塊

Plain Text

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>UserClassMain</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>

步驟四:啓動

代碼塊

Plain Text

java -javaagent:java-agent-demo-1.0-SNAPSHOT.jar -jar java-agent-demo-1.0-SNAPSHOT.jar

若是要傳入參數agentArgs,則以下:

代碼塊

Plain Text

java -javaagent:java-agent-demo-1.0-SNAPSHOT.jar=hello,world -jar java-agent-demo-1.0-SNAPSHOT.jar

注意,路徑後直接跟=號,後面參數不能有空格。

代碼示例

代碼塊

Plain Text

/*用戶的類*/
public class UserClass {
public int getNum() {
return 1;
}
}
 
/*用戶的main函數所在類*/
public class UserClassMain {
public static void main(String[] args) {
System.out.println("Execute User Class Main Method! result="+new UserClass().getNum());
}
}
 
/*Premain類*/
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println("agentArgs=" + agentArgs);
}
}

執行結果

2.2 Agentmain方式

Premain只能在main函數執行前進行處理,Agentmain則能夠在程序啓動後再運行。這就使得咱們給程序增長監控的時候能夠不用中止應用。

Agentmain和Premain的方式較類似,區別有三處:

一、Agentmain類

須要用Agentmain類替代以前的Premain類,Agentmain類也必須還有一個agentmain的函數:

代碼塊

Plain Text

public static void agentmain(String agentArgs, Instrumentation inst); //優先級更高,若二者都存在,只會執行該方法
public static void agentmain(String agentArgs);

二、添加manifest

相似的,添加manifest文件,屬性當中加入」 Agent-Class」」來指定步驟 1 當中編寫的那個帶有agentmain的Java類。 

代碼塊

Plain Text

Manifest-Version: 1.0
Agent-Class: Agentmain

三、啓動

要啓動agentmain,就須要引入一個新的包com.sun.tools.attach。Attach API 不是 Java 的標準 API,而是 Sun 公司提供的一套擴展 API,用來向目標 JVM 」附着」(Attach)代理工具程序的。有了它,開發者能夠方便的監控一個 JVM,運行一個外加的代理程序。

Attach API 很簡單,只有 2 個主要的類:

  • VirtualMachine 表明一個 Java 虛擬機,也就是程序須要監控的目標虛擬機,提供了 JVM 枚舉,Attach 動做和 Detach 動做(Attach 動做的相反行爲,從 JVM 上面解除一個代理)等等 ;

  • VirtualMachineDescriptor 則是一個描述虛擬機的容器類,配合 VirtualMachine 類完成各類功能。

如下的例子表示在啓動當前jvm中插入一個agent。

代碼塊

Plain Text

public class AgentLoader {
private static final String jarFilePath = "/Users/jiangxu/work/git/java-agent/target/java-agent-demo-1.0-SNAPSHOT.jar";
public static void loadAgent() {
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
int p = nameOfRunningVM.indexOf('@');
String pid = nameOfRunningVM.substring(0, p);
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(jarFilePath, "");
vm.detach();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
AgentLoader.loadAgent();
}
}

3 Instrumentation進階方法

Premain和Agentmain提供了兩種方式以供咱們提早或者在JVM運行時進行一些操做,可是如何對JVM中的類進行監控甚至修改,首先就須要用到兩個類:ClassFileTransformer和ClassDefinition。

3.1 ClassFileTransformer

ClassFileTransformer是一個接口,該接口有一個方法:

代碼塊

Plain Text

byte[] transform(  ClassLoader         loader,
String              className,
Class<?>            classBeingRedefined,
ProtectionDomain    protectionDomain,
byte[]              classfileBuffer) throws IllegalClassFormatException;

 

 

 

3.2 ClassDefinition

相關文章
相關標籤/搜索