Agent機制-整理

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(); }
    • 開發(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
    • 小節(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...
  • References

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容