agent有兩種: native(jvmti接口) 和 java層面的(instrumentation)
-
c/c++ 層面的 jvmti 接口
- jvmti官方文檔
- JVM TI是JDK提供的一套用于開發(fā)JVM監(jiān)控, 問題定位與性能調優(yōu)工具的通用編程接口(API)。 通過JVMTI,我們可以開發(fā)各式各樣的JVMTI Agent。這個Agent的表現(xiàn)形式是一個以c/c++語言編寫的動態(tài)共享庫
- JVMTI Agent原理
- Java啟動或運行時,動態(tài)加載一個外部基于JVM TI編寫的dynamic module到Java進程內(nèi),然后觸發(fā)JVM源生線程Attach Listener來執(zhí)行這個dynamic module的回調函數(shù)。在函數(shù)體內(nèi),你可以獲取各種各樣的VM級信息,注冊感興趣的VM事件,甚至控制VM的行為。
- jvmti api
- JVMTI是基于事件驅動的,JVM每執(zhí)行到一定的邏輯就會調用一些事件的回調接口(如果有的話),這些接口可以供開發(fā)者去擴展自己的邏輯。
- 可以獲取各種各樣的信息
- 開發(fā)jvm ti agent,簡單的來講,就是開發(fā)一個c/c++的共享庫。在windows下后綴是dll,linux/unix下是so,mac下就是dylib。所以我們創(chuàng)建工程和編譯環(huán)境的時候,記得以共享庫(share library)的形式來構建
- 兩種方式載入
- 隨java進程啟動時,自動載入共享庫
- 共享庫路徑是環(huán)境變量路徑: java -agentlib:foo=opt1,opt2,java啟動時會從linux的LD_LIBRARY_PATH或windows的PATH環(huán)境變量定義的路徑處裝載foo.so或foo.dll,找不到則拋異常
- 以絕對路徑的方式裝載共享庫: java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2 Sample
- java運行時,通過attach api動態(tài)載入
public static void main(String[] args) throws Exception { // args[0]為java進程id VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]); // args[1]為共享庫路徑,args[2]為傳遞給agent的參數(shù) virtualMachine.loadAgentPath(args[1], args[2]); virtualMachine.detach(); }
- 隨java進程啟動時,自動載入共享庫
- 開發(fā)jvmti agent
- jvmti.h頭文件里包含了所有jvm ti要用到的數(shù)據(jù)結構和回調函數(shù)定義
- 確定JVMTI的啟動方式
- JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM vm, char options, void *reserved)//啟動載入方式
- JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char options, void *reserved)//動態(tài)載入方式
- JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)//卸載都是一樣
- 具體例子可以google或jvmti官網(wǎng)上找
-
java層面的instrumentation
Instrumentation是Java5提供的新特性,使用Instrumentation,開發(fā)者可以構建一個代理,用來監(jiān)測運行在JVM上的程序
-
java.lang.instrument.ClassFileTransformer
- 每個代理類必須實現(xiàn) ClassFileTransformer接口,這個接口提供了一個transform方法:
- byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
- 通過這個方法,代理可以得到虛擬機載入的類的字節(jié)碼,并可對其進行修改,完成字節(jié)碼級的修改。
- classfileBuffer這個便是被代理類字節(jié)碼流,正是通過操作這個buffer完成對字節(jié)碼的修改
- 對于函數(shù)的返回值,如果返回null,則表示不對類的字節(jié)碼做任何的修改,否則應該返回修改過的byte[]對象
-
提供一個公共的靜態(tài)方法 public static void premain(String agentArgs, Instrumentation inst)
- 一般會在這個方法中創(chuàng)建一個代理對象,通過Instrumentation對象的addTransformer()方法,將創(chuàng)建的代理對象再傳遞給虛擬機
public class HelloWorld implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<>; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("java.lang.instrument, hello world!"); return null; } public static void premain(String args,Instrumentation inst){ inst.addTransformer(new HelloWorld()); } }
public class Example { public static void main(String[] args){ System.out.println("main class of proxy!"); } }- 將agent類HelloWorld編譯成可運行的jar(helloworld.jar),這里注意在manifest文件中添加premain入口,也即其配置為:
- Premain-Class: cn.dstrace.instrument.HelloWorld
- 運行
- java -javaagent:helloworld.jar cn.dstrace.instrument.Example
- 一般會在這個方法中創(chuàng)建一個代理對象,通過Instrumentation對象的addTransformer()方法,將創(chuàng)建的代理對象再傳遞給虛擬機
-
小節(jié)
- java的各種性能監(jiān)控工具中都有instrument的身影,如jconsole等
- 這樣的特性實際上提供了一種虛擬機級別支持的 AOP 實現(xiàn)方式
-
動態(tài)的javaagent
- 在 Java SE6 里面,最大的改變使運行時的 Instrumentation 成為可能;java attach api
- 但是在實際的很多的情況下,我們沒有辦法在虛擬機啟動之時就為其設定代理
- jdk5局限
- 在 Java SE 5 當中,開發(fā)者可以讓 Instrumentation 代理在 main 函數(shù)運行前執(zhí)行。
- 在 Java SE 5 當中,開發(fā)者只能在 premain 當中施展想象力,所作的 Instrumentation 也僅限與 main 函數(shù)執(zhí)行前,這樣的方式存在一定的局限性。
- 在 Java SE 5 的基礎上,Java SE 6 針對這種狀況做出了改進,開發(fā)者可以在main 函數(shù)開始執(zhí)行以后,再啟動自己的 Instrumentation 程序。
- 在 Java SE 6 的 Instrumentation 當中,有一個跟 premain“并駕齊驅”的“agentmain”方法,可以在 main 函數(shù)開始運行之后再運行。跟 premain 函數(shù)一樣, 開發(fā)者可以編寫一個含有“agentmain”函數(shù)的 Java 類:
- 在 Java SE 6 的新特性里面,有一個不太起眼的地方,揭示了 agentmain 的用法。這就是Java SE 6 當中提供的 Attach API
-
javaagent原理完全解讀
- javaagent的主要的功能如下
- 可以在加載class文件之前做攔截把字節(jié)碼做修改
- 可以在運行期將已經(jīng)加載的類的字節(jié)碼做變更,但是這種情況下會有很多的限制
- 還有其他的一些小眾的功能
- 獲取所有已經(jīng)被加載過的類
- 獲取所有已經(jīng)被初始化過了的類(執(zhí)行過了clinit方法,是上面的一個子集)
- 獲取某個對象的大小
- 將某個jar加入到bootstrapclasspath里作為高優(yōu)先級被bootstrapClassloader加載
- 將某個jar加入到classpath里供AppClassloard去加載
- 設置某些native方法的前綴,主要在查找native方法的時候做規(guī)則匹配
- 其實我們每天都在和JVMTIAgent打交道,只是你可能沒有意識到而已,比如我們經(jīng)常使用eclipse等工具對java代碼做調試,其實就利用了jre自帶的jdwp agent來實現(xiàn)的,只是由于eclipse等工具在沒讓你察覺的情況下將相關參數(shù)(類似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)給自動加到程序啟動參數(shù)列表里了,其中agentlib參數(shù)就是用來跟要加載的agent的名字,比如這里的jdwp(不過這不是動態(tài)庫的名字,而JVM是會做一些名稱上的擴展,比如在linux下會去找libjdwp.so的動態(tài)庫進行加載,也就是在名字的基礎上加前綴lib,再加后綴.so),接下來會跟一堆相關的參數(shù),會將這些參數(shù)傳給Agent_OnLoad或者Agent_OnAttach函數(shù)里對應的options參數(shù)。
- javaagent
- 說到javaagent必須要講的是一個叫做instrument的JVMTIAgent(linux下對應的動態(tài)庫是libinstrument.so),因為就是它來實現(xiàn)javaagent的功能的,另外instrument agent還有個別名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),從這名字里也完全體現(xiàn)了其最本質的功能:就是專門為java語言編寫的插樁服務提供支持的。
- instrument agent (libinstrument.so實現(xiàn))
- instrument agent實現(xiàn)了Agent_OnLoad和Agent_OnAttach兩方法
- instrument agent的核心數(shù)據(jù)結構如下
- tobecontinued...
- javaagent的主要的功能如下
-
References