java探针之修改类字节码文件
java探針利用了javaAgent + ASM字節(jié)碼注入工具實現(xiàn)了動態(tài)修改類文件的功能。像skywalking和arthas都使用到了這個技術(shù)。
具體原理為:
jdk1.5以后引入了javaAgent技術(shù),javaAgent是運行方法之前的攔截器。我們利用javaAgent和ASM字節(jié)碼技術(shù),在JVM加載class二進制文件的時候,利用ASM動態(tài)的修改加載的class文件,在監(jiān)控的方法前后添加計時器功能,用于計算監(jiān)控方法耗時,同時將方法耗時及內(nèi)部調(diào)用情況放入處理器,處理器利用棧先進后出的特點對方法調(diào)用先后順序做處理,當(dāng)一個請求處理結(jié)束后,將耗時方法軌跡和入?yún)ap輸出到文件中,然后根據(jù)map中相應(yīng)參數(shù)或耗時方法軌跡中的關(guān)鍵代碼區(qū)分出我們要抓取的耗時業(yè)務(wù)。最后將相應(yīng)耗時軌跡文件取下來,轉(zhuǎn)化為xml格式并進行解析,通過瀏覽器將代碼分層結(jié)構(gòu)展示出來,方便耗時分析。
上篇我們介紹了JavaAgent的基本使用,下面介紹如何去動態(tài)的修改類的字節(jié)碼文件,這個才是agent實現(xiàn)更強大功能的核心所在!
Instrumentation接口
Instrumentation接口位于jdk1.6包java.lang.instrument包下,Instrumentation指的是可以獨立于應(yīng)用程序之外的代理程序,可以用來監(jiān)控和擴展JVM上運行的應(yīng)用程序,相當(dāng)于是JVM層面的AOP。
功能:
監(jiān)控和擴展JVM上的運行程序,它可以替換和修改java類的字節(jié)碼以便采集數(shù)據(jù),用于監(jiān)控,性能統(tǒng)計,覆蓋率分析,事件記錄等??梢杂迷诔绦騿訒r,也可以用于程序運行時動態(tài)attach。
比如說一個Java程序在JVM上運行,這時如果需要監(jiān)控JVM的狀態(tài),除了使用JDK自帶的jps等命令之外,就可以通過instrument來更直觀的獲取JVM的運行情況;
或者一個Java方法在JVM中執(zhí)行,如果我想獲取這個方法的執(zhí)行時間又不想改代碼,常用的做法是通過Spring的AOP來實現(xiàn),而AOP通過面向切面編程,而instrument是在JVM層面上直接改動java方法來實現(xiàn)。
要是定義了操作java類的class文件方法,這里又涉及到了ClassFileTransformer接口,這個接口的作用是改變Class文件的字節(jié)碼,返回新的字節(jié)碼數(shù)組,源碼如下:
public interface ClassFileTransformer{byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException; }ClassFileTransformer接口只有一個方法,就是改變指定類的Class文件,該接口沒有默認實現(xiàn),很顯然如果需要改變Class文件的內(nèi)容,需要改成什么樣需要使用者自己來實現(xiàn)。
如:
然后在permain或agentmain方法中inst.addTransformer(new MyTransformer());,其他步驟同之前,不再贅述。
Instrumentation接口相當(dāng)于一個代理,當(dāng)執(zhí)行premain方法時,通過Instrumentation提供的API可以動態(tài)的添加管理JVM加載的Class文件,Instrumentation管理著ClassFileTransformer。
ClassFileTransformer接口可以動態(tài)的改變Class文件的字節(jié)碼,在加載字節(jié)碼的時候可以將字節(jié)碼進行動態(tài)修改,具體實現(xiàn)需要自定義實現(xiàn)類來實現(xiàn)ClassFileTransformer接口
Java字節(jié)碼生成框架大致有ASM、Javassist和byte buddy三種
-
ASM框架介紹及使用
ASM是一種Java字節(jié)碼操控框架,能夠以二進制形式修改已有的類或是生成類,ASM可以直接生成二進制class文件也可以在類被加載入JVM之前動態(tài)改變類,只不過ASM在創(chuàng)建class字節(jié)碼時說底層JVM的匯編指令,需要使用者對class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。由于Java 類存儲在.class文件中,這些類文件中包含有:類名稱、方法、屬性及字節(jié)碼,ASM從類文件中讀入信息后改變類行為、分析類信息或者直接創(chuàng)建新的類。著名的使用到ASM的案例便是lambda表達式、CGLIB動態(tài)代理類
ASM框架核心類包含
ClassReader:該類用來解析編譯過的class字節(jié)碼文件
ClassWriter:該類用來重新構(gòu)建編譯后的類,比如修改類名、屬性、方法或者根據(jù)要求創(chuàng)建新的字節(jié)碼文件
ClassAdapter:實現(xiàn)了ClassVisitor接口,將對它的方法調(diào)用委托給另一個ClassVisitor對象 -
Javassist及使用
Javassit相比于ASM要簡單點,Javassit提供了更高級的API,當(dāng)時執(zhí)行效率上比ASM要差,因為ASM上直接操作的字節(jié)碼。功能和JDK自帶的反射功能類似,但是比反射要強大。Javassist核心類包括ClassPool:
一個基于HashMap實現(xiàn)的CtClass對象容器,key上類名,value上這個類的CtClass對象
CtClass:表示一個類,可以從ClassPool中獲取
CtMethods:表示一個類的方法
CtFields:表示類中的屬性 -
Byte Buddy及使用
byte buddy是一個提供了API用于生成任意Java類工具包,可以生成和修改字節(jié)碼。
3. Instrumentation的實現(xiàn)原理
說起Instrumentation的原理,就不得不先提起JVMTI:
JVMTI官網(wǎng)文檔
JVMTI
JVMTI 是JVM Tool Interface 的縮寫,是 JVM 暴露出來給用戶擴展使用的接口集合,JVMTI 是基于事件驅(qū)動的,JVM每執(zhí)行一定的邏輯就會調(diào)用一些事件的回調(diào)接口,這些接口可以給用戶自行擴展來實現(xiàn)自己的邏輯。JVMTI是實現(xiàn) Debugger、Profiler、Monitor、Thread Analyser 和coverage analysis等工具的統(tǒng)一基礎(chǔ),在主流 Java 虛擬機中都有實現(xiàn)。
JVMTIAgent
JVMTI 是一套本地代碼接口,因此使用 JVMTI 需要我們與 C/C++ 以及 JNI 打交道。事實上,開發(fā)時一般采用建立一個 Agent 的方式來使用 JVMTI,它使用 JVMTI 函數(shù),設(shè)置一些回調(diào)函數(shù),并從 Java 虛擬機中得到當(dāng)前的運行態(tài)信息,并作出自己的判斷,最后還可能操作虛擬機的運行態(tài)。把 Agent 編譯成一個動態(tài)鏈接庫之后,我們就可以在 Java 程序啟動的時候來加載它(啟動加載模式)
主要有三個函數(shù):
- Agent_OnLoad方法:如果agent是在啟動時加載的,那么在JVM啟動過程中會執(zhí)行這個agent里的Agent_OnLoad函數(shù)
- Agent_OnAttach方法:如果agent不是在啟動時加載的,而是attach到目標程序上,然后通過load命令來加載agent,由ClassFileLoadHook event提供回調(diào),調(diào)用Agent_OnAttach方法
- Agent_OnUnload方法:在agent卸載時調(diào)用
回到主題,Instrument 就是一種 JVMTIAgent,它實現(xiàn)了Agent_OnLoad和Agent_OnAttach兩個方法,也就是在使用時,Instrument既可以在啟動時加載,也可以在運行時動態(tài)加載
- 啟動時加載就是在啟動時添加JVM參數(shù):-javaagent:XXXAgent.jar的方式
- 運行時加載是通過JVM的attach機制來實現(xiàn),通過發(fā)送load命令來加載,這種方式明顯更加靈活,對監(jiān)控目標啟動也無限制,arthas的attach就是基于此
通過 VirtualMachine , 可以attach到當(dāng)前指定的jvm pid上,然后 virtualMachine.loadAgent()將編寫好的agent用于監(jiān)控目標。
總結(jié):
Instrumentation相當(dāng)于一個JVM級別的AOP
Instrumentation在JVM啟動的時候監(jiān)聽事件,如類加載事件,JVM觸發(fā)來指定的事件通過回調(diào)通知,并創(chuàng)建一個 Instrumentation接口的實例,然后找到MANIFEST.MF中配置的實現(xiàn)了premain方法的Class,然后將Instrumentation實例傳入premain方法中
premain方法會在main方法之前執(zhí)行,可以添加ClassFileTransfer來實現(xiàn)對Class文件字節(jié)碼的動態(tài)修改(并不會修改Class文件中的字節(jié)碼,而是修改已經(jīng)被JVM加載的字節(jié)碼)
修改字節(jié)碼的技術(shù)可以使用開源的 ASM、javassist、byteBuddy等
https://blog.csdn.net/u010862794/article/details/87773434
總結(jié)
以上是生活随笔為你收集整理的java探针之修改类字节码文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring容器实例bean中的几个关键
- 下一篇: 重要的基础注解@import