Java動態追蹤技術探究html
能夠用於在類加載的時候,修改字節碼。github
利用javaAgent和ASM字節碼技術開發java探針工具,實現原理以下:面試
jdk1.5之後引入了javaAgent技術,javaAgent是運行方法以前的攔截器。咱們利用javaAgent和ASM字節碼技術,在JVM加載class二進制文件的時候,利用ASM動態的修改加載的class文件,在監控的方法先後添加計時器功能,用於計算監控方法耗時,同時將方法耗時及內部調用狀況放入處理器,處理器利用棧先進後出的特色對方法調用前後順序作處理,當一個請求處理結束後,將耗時方法軌跡和入參map輸出到文件中,而後根據map中相應參數或耗時方法軌跡中的關鍵代碼區分出咱們要抓取的耗時業務。最後將相應耗時軌跡文件取下來,轉化爲xml格式並進行解析,經過瀏覽器將代碼分層結構展現出來,方便耗時分析。編程
Java對象的方法、函數存放在方法區。方法區中的數據是類加載時從class文件中提取出來的。class文件是咱們寫的文件編譯而來。瀏覽器
咱們能夠在加載class字節碼文件時,動態修改這個文件,從新加載class文件。可是不改變對象的屬性,也不影響已經存在對象的狀態。安全
Instrumentation接口,裏面有2個方法:redefineClasses和retransformClasses。架構
一個是從新定義class,一個是修改class。這兩個大同小異,都是替換已經存在的class文件,redefineClasses是本身提供字節碼文件替換掉已存在的class文件,retransformClasses是在已存在的字節碼文件上修改後再替換之。框架
固然在運行時直接替換很不安全。好比新引用不安全的類,刪除某個屬性,這些狀況會發生異常,因此Instrumentation有不少限制,可是在方法先後加日誌卻是夠了。
如今,咱們要修改字節碼class文件。固然,最簡單的是把修改後的Java文件從新編譯一遍獲得class文件,而後調用redefineClasses替換。可是,不少時候,咱們拿不到這個源碼文件。
咱們都知道,Spring的AOP是基於動態代理實現的,Spring會在運行時動態建立代理類,代理類中引用被代理類,在被代理的方法執行先後進行一些神祕的操做。那麼,Spring是怎麼在運行時建立代理類的呢?動態代理的美妙之處,就在於咱們沒必要手動爲每一個須要被代理的類寫代理類代碼,Spring在運行時會根據須要動態地創造出一個類,這裏創造的過程並不是經過字符串寫Java文件,而後編譯成class文件,而後加載。Spring會直接「創造」一個class文件,而後加載,創造class文件的工具,就是ASM了。
Attach API 不是 Java 的標準 API,而是 Sun 公司提供的一套擴展 API,用來向目標 JVM 」附着」(Attach)代理工具程序的。有了它,開發者能夠方便的監控一個 JVM,運行一個外加的代理程序。
Attach API 很簡單,只有 2 個主要的類,都在 com.sun.tools.attach 包裏面: VirtualMachine 表明一個 Java 虛擬機,也就是程序須要監控的目標虛擬機,提供了 JVM 枚舉,Attach 動做和 Detach 動做(Attach 動做的相反行爲,從 JVM 上面解除一個代理)等等 ; VirtualMachineDescriptor 則是一個描述虛擬機的容器類,配合 VirtualMachine 類完成各類功能。
相對於jdk5只能經過啓動腳本添加javaagent的方式植入代理,jdk6的動態attach也只是免去了修改啓動腳本和不用重啓的工做,並無添加其它新的特性,即便不使用動態attach而是使用腳本添加javaagent的方式也能夠達到隨時修改class定義的目的,不管經過什麼方式咱們只要獲取了 Instrumentation 實例,而後調用其addTransformer方法添加類轉換器再調用retransformClasses就能夠轉換一個類的字節序列了,這裏須要注意的是retransformClasses是jdk1.6定義的,jdk1.5只能使用redefineClasses,retransformClasses功能強大使用簡單,但有不能修改方法簽名,只能修改body等約束,redefineClasses則是一個可定細節制化的選擇。
BTrace是基於Java語言的一個安全的、可提供動態追蹤服務的工具。BTrace基於ASM、Java Attach Api、Instruments開發,爲用戶提供了不少註解。依靠這些註解,咱們能夠編寫BTrace腳本(簡單的Java代碼)達到咱們想要的效果,而沒必要深陷於ASM對字節碼的操做中不可自拔。
BTrace主要有下面幾個模塊:
整個BTrace的架構大體以下:
BTrace最終借Instruments實現class的替換。如上文所說,出於安全考慮,Instruments在使用上存在諸多的限制,BTrace也不例外。
BTrace腳本在使用上有必定的學習成本,若是能把一些經常使用的功能封裝起來,對外直接提供簡單的命令便可操做的話,那就再好不過了。阿里的工程師們早已想到這一點,就在去年(2018年9月份),阿里巴巴開源了本身的Java診斷工具——Arthas。Arthas提供簡單的命令行操做,功能強大。究其背後的技術原理,和本文中提到的大體無二。Arthas的文檔很全面,想詳細瞭解的話能夠戳這裏。
動態編程是相對於靜態編程而言的,平時咱們討論比較多的就是靜態編程語言,例如Java,與動態編程語言,例如JavaScript。那兩者有什麼明顯的區別呢?簡單的說就是在靜態編程中,類型檢查是在編譯時完成的,而動態編程中類型檢查是在運行時完成的。所謂動態編程就是繞過編譯過程在運行時進行操做的技術,在Java中有以下幾種方式:
反射:原理也就是經過在運行時得到類型信息而後作相應的操做。
動態編譯:動態編譯是從Java 6
開始支持的,主要是經過一個JavaCompiler
接口來完成的。經過這種方式咱們能夠直接編譯一個已經存在的java文件,也能夠在內存中動態生成Java代碼,動態編譯執行。
調用JavaScript引擎:Java 6
加入了對Script(JSR223)
的支持。這是一個腳本框架,提供了讓腳本語言來訪問Java內部的方法。你能夠在運行的時候找到腳本引擎,而後調用這個引擎去執行腳本。這個腳本API容許你爲腳本語言提供Java支持。
動態生成字節碼:這種技術經過操做Java字節碼的方式在JVM
中生成新類或者對已經加載的類動態添加元素。
操做java字節碼的工具備兩個比較流行,一個是ASM,一個是Javassit 。原理也都是利用了Java的設計原理:存在一個虛擬機執行字節碼。這就使咱們在此處有了改變字節碼的操做空間。
ASM :直接操做字節碼指令,執行效率高,要是使用者掌握Java類字節碼文件格式及指令,對使用者的要求比較高。
Javassist: 提供了更高級的API,執行效率相對較差,但無需掌握字節碼指令的知識,對使用者要求較低。
應用層面來說通常使用建議優先選擇Javassit,若是後續發現Javassit 成爲了整個應用的效率瓶頸的話能夠再考慮ASM.固然若是開發的是一個基礎類庫,或者基礎平臺,仍是直接使用ASM。