tags: java, troubleshooting, monitor,btracejava
一句話歸納:
BTrace
是一個是強大的java線上應用檢測工具(動態追蹤工具),能夠在不修改應用代碼,不停應用服務的前提下檢測代碼運行狀況,進而診斷問題,是生產環境下必備神器,本文將對它的使用進行講解。git
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
進行運行原理和使用進行描述。github
BTrace
是基於java的動態追蹤技術來實現的。對於java開發人員,都清楚java程序的開發流程是寫java代碼,把它編譯爲class文件,而後在JVM
中加載class運行。若此時想要在不中止應用的狀況下對class
進行修改來添加追蹤內容,如在某個方法(method
)中添加輸出信息,主要是兩件事情:api
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
)對動態追蹤技術進行了詳細說明,下面簡要說明一下。bash
BTrace腳本:利用BTrace定義的註解,咱們能夠很方便地根據須要進行腳本的開發。服務器
Compiler:將BTrace腳本編譯成BTrace class文件。app
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
btrace
的命令在bin
目錄 下btrace
設置到環境變量中(export
)基本上,BTrace
只適用於動態追蹤類的輸出信息,不能添加屬性、刪除方法,修改繼承等,這跟前面提到的Instrument
的限制是一致的。通常來講,使用Btrace
進行線上應用監測,基於都屬於日誌輸出類,多數包括如下幾大場景:
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對象
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
的瞭解,更方便、有效率地解決線上問題。
BTrace
官網:https://github.com/btraceio/btrace
BTrace
註解:https://github.com/btraceio/btrace/wiki/BTrace-Annotations
https://github.com/mianshenglee/my-example/tree/master/java-monitor-example
https://mp.weixin.qq.com/s/_hSaI5yMvPTWxvFgl-UItA
https://www.rowkey.me/blog/2016/09/20/btrace/