使用 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
Premain即爲Java 5中的在運行前設置代理類的方式。函數
咱們能夠在一個普通 Java 程序(帶有 main 函數的 Java 類)運行時,經過 – javaagent
參數指定一個特定的 jar 文件(包含 Instrumentation 代理)來啓動 Instrumentation 的代理程序。工具
編寫一個 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 函數中,開發者能夠進行對類的各類操做。
在src/main/resources/META-INF中建立MANIFEST.MF文件,屬性當中加入」 Premain-Class」來指定步驟 1 當中編寫的那個帶有 premain 的 Java 類。
代碼塊
Plain Text
Manifest-Version: 1.0
Premain-Class: Premain
若是是用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);
}
}
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();
}
}
Premain和Agentmain提供了兩種方式以供咱們提早或者在JVM運行時進行一些操做,可是如何對JVM中的類進行監控甚至修改,首先就須要用到兩個類:ClassFileTransformer和ClassDefinition。
ClassFileTransformer是一個接口,該接口有一個方法:
代碼塊
Plain Text
byte[] transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException;
3.2 ClassDefinition