您好,登錄后才能下訂單哦!
這篇文章主要講解了“java用BTrace實現在線動態診斷”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java用BTrace實現在線動態診斷”吧!
一句話概括:
BTrace
是一個是強大的java線上應用檢測工具(動態追蹤工具),可以在不修改應用代碼,不停應用服務的前提下檢測代碼運行情況,進而診斷問題,是生產環境下必備神器,本文將對它的使用進行講解。
BTrace
是一款開源軟件,github
地址為:https://github.com/btraceio/btrace
,官網的介紹是BTrace is a safe, dynamic tracing tool for the Java platform.
,它是安全的動態追蹤java應用的工具,即可以動態地向目標應用的字節碼注入追蹤代碼。何為動態?我們都知道,即在java應用啟動的時候會把class
文件加載到JVM
運行,此時class
代碼功能是確定、靜態的(無法變更),要想修改,只能是修改代碼,重新編譯、部署、啟動。
而在處理線上應用時,我們經常需要查看代碼運行情況,參數值、返回值查看,或者添加自己需要調試的日志等,在開發階段,添加日志,重新啟動沒有問題,但在生產環境就不適用了(生產環境一般不輕易關停服務,而且即使可以重啟,可能發生問題的現場就破壞了,無法重現問題),那么是否有方法在java應用運行期間,不重啟程序的情況,動態加入自己想要監測(追蹤)的內容?Btrace
就是這樣一個動態追蹤神器,可以在不用重啟的情況下監控應用運行情況,可以獲取程序運行時的數據信息,如方法參數、返回值、全局變量和堆棧信息等。本文就是對BTrace
進行運行原理和使用進行描述。
BTrace
是基于java的動態追蹤技術來實現的。對于java開發人員,都清楚java程序的開發流程是寫java代碼,把它編譯為class文件,然后在JVM
中加載class運行。若此時想要在不停止應用的情況下對class
進行修改來添加追蹤內容,如在某個方法(method
)中添加輸出信息,主要是兩件事情:
(1)修改已經加載到JVM
中的class,添加自定義輸出
(2)替換運行在JVM`中的class
第一步,修改,由于JVM
運行的都是class文件,是不是可以直接修改字節碼class
文件就行了(當然,字節碼文件的可讀性遠遠沒有Java代碼高),但是已經有相應的框架可以做這件事,就是ASM
,利用這框架,可以直接編輯字節碼的框架,它也提供接口可以讓我們方便地操作字節碼文件,進行注入修改類的方法,動態創造一個新的類等等。Spring
就是使用這種技術來實現動態代理的。
第二步,替換,如果對它進行替換,則需要用到java提供的java.lang.instrument.Instrumentation
,它有兩個接口redefineClasses
和retransformClasses
,redefineClasses
是自己提供字節碼文件替換掉已存在的class文件,retransformClasses
是在已存在的字節碼文件上修改后再替換。不過需要注意的是instrument
的使用有限制的(不能添加、修改、刪除已經有字段和方法,不能改變方法簽名,改變繼承屬性等):
The redefinition must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance
BTrace就是基于前面的技術來實現的,文章《Java動態追蹤技術探究》(https://mp.weixin.qq.com/s/_hSaI5yMvPTWxvFgl-UItA
)對動態追蹤技術進行了詳細說明,下面簡要說明一下。
BTrace腳本:利用BTrace定義的注解,我們可以很方便地根據需要進行腳本的開發。
Compiler:將BTrace腳本編譯成BTrace class文件。
Client:將class文件發送到Agent。
Agent:基于Java的Attach API
,Agent可以動態附著到一個運行的JVM
上,然后開啟一個BTrace Server
,接收client發過來的BTrace腳本;解析腳本,然后根據腳本中的規則找到要修改的類;修改字節碼后,調用Java Instrument
的retransform
接口,完成對對象行為的修改并使之生效。
運行流程圖如下:
跟java源碼一樣,先編寫Btrace
腳本(也是java文件),編譯(compiler
),通過client發送給agent
,agent
通過attach api
添加到JVM并啟動agent server
來接收client
發送過來的內容,然后底層是使用ASM
修改字節碼文件,之后使用Java Instrument
的retransform
接口替換修改后的class文件,運行后的輸出再通過agent
發送到client
進行顯示。
知道了BTrace
的運行原理,現在可以安裝實踐一下。本文用的示例還是java-monitor-example
。BTrace
的安裝很簡單,開箱即用。
下載地址(當前最新版本是[v1.3.11.3]
):https://github.com/btraceio/btrace/releases
解壓到需要監測的java應用所在服務器中
btrace
的命令在bin
目錄 下
若需要在任意目錄可執行,需要把btrace
設置到環境變量中(export
)
基本上,BTrace
只適用于動態追蹤類的輸出信息,不能添加屬性、刪除方法,修改繼承等,這跟前面提到的Instrument
的限制是一致的。一般來說,使用Btrace
進行線上應用監測,基于都屬于日志輸出類,多數包括以下幾大場景:
查看某一個方法中入參和返回值
查看某一個方法的響應時間
查看某行代碼是否有執行到
打印系統參數或JVM啟動參數
打印方法調用的線程堆棧
出現異常時打印出現異常信息
Btrace
作為一個獨立運行的工具,默認只能在本地運行,也就是說,想要監測哪個正在運行的java應用,就需要把它解壓到對應的服務器。本示例中運行的是java-monitor-example
作為需要監測的java應用,然后就是根據監測業務需求,寫腳本,運行腳本,查看輸出了。
Btrace
的腳本與編寫java代碼無異,不過相對簡單很多,主要是使用Btrace
提供的注解和BTraceUtils
,注解用于告訴Btrace
需要攔截的類、攔截時機、攔截位置等,BTraceUtils
用于提供打印輸出種信息的功能。如官網給出的示例如下:
package samples; import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * This script traces method entry into every method of * every class in javax.swing package! Think before using * this script -- this will slow down your app significantly!! */ @BTrace public class AllMethods { @OnMethod( clazz="/javax\\.swing\\..*/", method="/.*/" ) public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod) { print(Strings.strcat("entered ", probeClass)); println(Strings.strcat(".", probeMethod)); } }
以上代碼,表示,會攔截所有調用以javax.swing
開頭的方法,然后打印出類名和方法名。可以注意到注解有@BTrace
、@OnMethod
、@ProbeClassName
,@ProbeMethodName
,而print
,println
是BTraceUtils
提供的靜態方法。BTraceUtils
還提供了很多打印方法(后面示例會提到)。另外,還要注意的是跟蹤操作都需要在靜態方法體內指定,因此都需要static
方法。
另外,關于BTrace
提供的注解,詳細可以參考官方文檔(https://github.com/btraceio/btrace/wiki/BTrace-Annotations
)。主要包括以下:
/**Class Annotations*/ @com.sun.btrace.annotations.DTrace @com.sun.btrace.annotations.DTraceRef @com.sun.btrace.annotations.BTrace /**Method Annotations*/ @com.sun.btrace.annotations.OnMethod @com.sun.btrace.annotations.OnTimer @com.sun.btrace.annotations.OnError @com.sun.btrace.annotations.OnExit @com.sun.btrace.annotations.OnEvent @com.sun.btrace.annotations.OnLowMemory @com.sun.btrace.annotations.OnProbe /**Argument Annotations*/ @com.sun.btrace.annotations.Self @com.sun.btrace.annotations.Return @com.sun.btrace.annotations.CalledInstance @com.sun.btrace.annotations.CalledMethod /**Field Annotations*/ @com.sun.btrace.annotations.Export @com.sun.btrace.annotations.Property @com.sun.btrace.annotations.TLS
其中,@OnMethod
用得比較多,需要重點說明一下,它主要是三個屬性clazz
,method
和location
。
clazz
:類的全路徑名,如me.mason.monitor.controller.UserController
method
:要監測的方法名,如getUsers
location
:攔截時機,使用@Location
注解。
@Location
又有以下幾種:
Kind.ENTRY
:在進入方法時調用
Kind.RETURN
:方法執行完時調用,只有把攔截位置定義為Kind.RETURN
,才能獲取方法的返回結果@Return
和執行時間@Duration
Kind.CALL
:方法中調用其它方法時調用
Kind.LINE
:通過設置line,可以監控代碼是否執行到指定的位置
Kind.ERROR, Kind.THROW, Kind.CATCH
:異常情況的跟蹤
建議還是使用java的maven項目的開發環境進行編寫,可以使用代碼提示功能。寫好后再放到對應需要監測的服務器中。不過編輯時需要引用對應的jar包(btrace-agent
,btrace-boot
,btrace-client
),對應的jar在下載的安裝下的build
目錄下。通過pom.xml
引入即可使用。如下所示:
<!-- BTrace --> <dependency> <groupId>com.sun.btrace</groupId> <artifactId>btrace-agent</artifactId> <version>1.3.11.3</version> <type>jar</type> <scope>system</scope> <systemPath>E:/btrace-bin-1.3.11.3/build/btrace-agent.jar</systemPath> </dependency> <dependency> <groupId>com.sun.btrace</groupId> <artifactId>btrace-boot</artifactId> <version>1.3.11.3</version> <type>jar</type> <scope>system</scope> <systemPath>E:/btrace-bin-1.3.11.3/build/btrace-boot.jar</systemPath> </dependency> <dependency> <groupId>com.sun.btrace</groupId> <artifactId>btrace-client</artifactId> <version>1.3.11.3</version> <type>jar</type> <scope>system</scope> <systemPath>E:/btrace-bin-1.3.11.3/build/btrace-client.jar</systemPath> </dependency>
打印幫助信息如下:
一般來說,在服務器上,直接是btrace PID btraceFile.java
,然后查看輸出(也可以把內容輸出到文件中再查看,如btrace PID btraceFile.java > info.txt
)。如果有使用到特定的jar包,則需要把參數cp
或classpath
加上。如下示例是把調用方法的返回值進行輸出:
下面通過幾個常用的示例來說明一下BTrace
腳本的使用,腳本在示例工程java-monitor-example
中的btrace
目錄下。java-monitor-example
中,分別是一個controller
和service
,有如下方法定義,下面會根據這些方法進行動態追蹤。
/** * UserController.java **/ @GetMapping("/user") public ResponseResult<User> getUser() { User user = userService.getUser(); return ResponseResult.ok(user); } @GetMapping("/users") public ResponseResult<User> getUsers(int num) { List<User> users = userService.getUsers(num); return ResponseResult.ok(users); } /** * UserService.java * 根據ID獲取用戶 * * @return */ public User getUser() { return mockUser(); } /** * 獲取用戶數組 * * @return */ public List<User> getUsers(int num) { userList.clear(); for(int i=0 ; i < num; i++){ userList.add(mockUser()); } return userList; }
打印調用方法時的參數(調用UserController
的getUsers
方法時打印)
@OnMethod(clazz = "me.mason.monitor.controller.UserController" ,method = "getUsers",location = @Location(Kind.ENTRY)) public static void readFunction(@ProbeClassName String className, @ProbeMethodName String methodName, AnyType[] args) { // 打印時間 BTraceUtils.println(BTraceUtils.Time.timestamp("yyyy-MM-dd HH:mm:ss")); BTraceUtils.println("method controller"); BTraceUtils.printArray(args); BTraceUtils.println(className + "," + methodName); BTraceUtils.println("=========================="); }
打印調用方法時的返回值
@OnMethod(clazz = "me.mason.monitor.service.UserService" ,method = "getUsers",location = @Location(Kind.RETURN)) public static void printReturnData1(@Return AnyType result){ BTraceUtils.println(BTraceUtils.Time.timestamp("yyyy-MM-dd HH:mm:ss")); BTraceUtils.printFields(result); BTraceUtils.println("=========================="); BTraceUtils.println(BTraceUtils.str(result)); BTraceUtils.println("=========================="); }
執行到的行數(查看是否執行到UserService
的39行)
@OnMethod(clazz = "me.mason.monitor.service.UserService" ,method = "getUsers",location = @Location(value = Kind.LINE,line = 39)) public static void printLineData(@ProbeClassName String className, @ProbeMethodName String methodName,int line){ BTraceUtils.println(BTraceUtils.Time.timestamp("yyyy-MM-dd HH:mm:ss")); BTraceUtils.println(className + "," + methodName + ","+line); BTraceUtils.println("=========================="); }
執行方法的用時(UserController
的getUsers
方法用時多長)
@OnMethod(clazz = "me.mason.monitor.controller.UserController" ,method = "getUsers",location = @Location(Kind.RETURN)) public static void getUsersDuration(@Duration long duration){ BTraceUtils.println(BTraceUtils.Time.timestamp("yyyy-MM-dd HH:mm:ss")); BTraceUtils.println("time(ns):" + duration); BTraceUtils.println("time(ms):" + BTraceUtils.str(duration / 1000000)); BTraceUtils.println("time(s):" + BTraceUtils.str(duration / 1000000000)); BTraceUtils.println("=========================="); }
類似JDK
的命令行工具jinfo
,另外jmap
及jstatck
可查詢官方示例。
@BTrace public class JInfo { static { println("System Properties:"); printProperties(); println("VM Flags:"); printVmArguments(); println("OS Enviroment:"); printEnv(); exit(0); } }
java
開發人員應該都知道,java
的異常分為Error
和Exception
,而它們都是Throwable
的子類,即java
中所有異常的父類都Throwable
,因此追蹤這個的構造函數,然后把堆棧打印出來即可。如下:
//局部變量存儲異常 @TLS static Throwable currentException; //異常構造函數開始 @OnMethod( clazz="java.lang.Throwable", method="<init>" ) public static void onthrow(@Self Throwable self) { currentException = self; } //異常構造函數結束,輸出堆棧 @OnMethod( clazz="java.lang.Throwable", method="<init>", location=@Location(Kind.RETURN) ) public static void onthrowreturn() { if (currentException != null) { Threads.jstack(currentException); println("====================="); currentException = null; } }
BTrace
對JVM來說是“只讀的”,BTrace要做的是,雖然修改了字節碼,但是主要是輸出需要的信息,對整個程序的正常運行并沒有影響。需要注意的是,由于是動態替換class文件,被修改的字節碼是不會自動還原的。官方文檔也有說明,BTrace
腳本會有以下限制:
不允許創建對象
不允許創建數組
不允許拋異常
不允許catch異常
不允許隨意調用其他對象或者類的方法,只允許調用com.sun.btrace.BTraceUtils中提供的靜態方法(一些數據處理和信息輸出工具)
不允許改變類的屬性
不允許有成員變量和方法,只允許存在static public void方法
不允許有內部類、嵌套類
不允許有同步方法和同步塊
不允許有循環
不允許隨意繼承其他類(當然,java.lang.Object除外)
不允許實現接口
不允許使用assert
不允許使用Class對象
搭建使用java的maven項目的開發環境進行腳本編寫,引入相應的jar,以提供代碼提示功能。
查看官方提供的例子,在下載包中已提供例子,位置:btrace-bin-1.3.11.3\samples
目錄
BTrace
腳本中追蹤的輸入參數,返回值類型是簡單類型直接使用(如int ,float等),復雜類型可以使用AnyType
,但如果是使用自定義包中的類型(如User),則需要運行腳本時添加cp
或classpath
參數,指定自定義包。
一般簡單類型或字符串,直接使用print
或println
,打印對象屬性可使用printFields
,打印List
,可以使用BTraceUtils.println(BTraceUtils.str(list))
在探查方法的最后一行打印分隔,強烈建議。可能是由于輸出有緩沖區延遲,如果不輸出分隔,有可能會無法輸出或者輸出后內容沒有分隔。分隔可使用BTraceUtils.println
或BTraceUtils.println("============")
。
對于線上的java應用,如果想不停服務進行日志輸出來診斷問題,動態追蹤技術是必不可少的技術,而Btrace
是使用此技術來實現動態追蹤的有力工具。本文從Btrace
的運行原理、安裝、適用場景、腳本編寫、運行等方面進行了詳細描述,希望可以幫助大家加深Btrace
的了解,更方便、有效率地解決線上問題。
感謝各位的閱讀,以上就是“java用BTrace實現在線動態診斷”的內容了,經過本文的學習后,相信大家對java用BTrace實現在線動態診斷這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。