在生產環境中經常遇到格式各樣的問題,如OOM或者莫名其妙的進程死掉。一般情況下是通過修改程序,添加打印日志;然后重新發布程序來完成。然而,這不僅麻煩,而且帶來很多不可控的因素。有沒有一種方式,在不修改原有運行程序的情況下獲取運行時的數據信息呢?如方法參數、返回值、全局變量、堆棧信息等。Btrace就是這樣一個工具,它可以在不修改原有代碼的情況下動態地追蹤java運行程序,通過hotswap技術,動態將跟蹤字節碼注入到運行類中,對運行代碼侵入較小,對性能上的影響可以忽略不計。
基礎說明
由於Btrace會把腳本邏輯直接侵入到運行的代碼中,所以在使用上做很多限制:
- 不能創建對象
- 不能使用數組
- 不能拋出或捕獲異常
- 不能使用循環
- 不能使用synchronized關鍵字
- 屬性和方法必須使用static修飾
需要特別注意的是:不恰當的使用BTrace可能導致JVM崩潰,如在BTrace腳本使用錯誤的class文件,所以在上生產環境之前,務必在本地充分的驗證腳本的正確性。
Btrace可以做什么?
- 接口性能變慢,分析每個方法的耗時情況;
- 當在Map中插入大量數據,分析其擴容情況;
- 分析哪個方法調用了System.gc(),調用棧如何;
- 執行某個方法拋出異常時,分析運行時參數;
- ....
參數說明:
指定分析方法的入口:@OnMethod
Btrace使用@OnMethod注解定義需要分析的方法入口
在@OnMethod注解中,需要指定class、method以及location等,class表明需要監控的類,method表明需要監控的方法,指定方式如下:
- 使用全限定名:clazz="com.metty.rpc.common.BtraceCase", method="add"
- 使用正則表達式:clazz="/javax.swing../", method="/./"
- 使用接口:clazz="+com.ctrip.demo.Filter", method="doFilter"
- 使用注解:clazz="@javax.jws.WebService", method=""@javax.jws.WebMethod"
- 如果需要分析構造方法,需要指定method="
"
指定方法攔截的位置:@Location
定義Btrace對方法的攔截位置,通過@Location注解指定,默認為Kind.ENTRY
- Kind.ENTRY:在進入方法時,調用Btrace腳本
- Kind.RETURN:方法執行完時,調用Btrace腳本,只有把攔截位置定義為Kind.RETURN,才能獲取方法的返回結果@Return和執行時間@Duration
- Kind.CALL:分析方法中調用其它方法的執行情況,比如在execute方法中,想獲取add方法的執行耗時,必須把where設置成Where.AFTER
- Kind.LINE:通過設置line,可以監控代碼是否執行到指定的位置
- Kind.ERROR, Kind.THROW, Kind.CATCH
總結
Btrace能做的事情太多,但使用之前切記檢查腳本的可行性,一旦Btrace腳本侵入到系統中,只有通過重啟才能恢復。
通過jvisualvm插件的方式進行測試:
安裝Btrace插件
工具-->插件-->可用插件中找到BTrace Workbench進行安裝即可。
測試用例
package com.vmtools;
public class Counter {
// 總數
private static int totalCount = 0;
public int add(int num) throws Exception {
totalCount += num;
sleep();
return totalCount;
}
private void sleep() throws InterruptedException {
Thread.sleep(1000);
}
}
package com.vmtools;
import java.util.Random;
public class BtraceTest {
public static void main(String[] args) throws Exception {
Random random = new Random();
// 計數器
Counter counter = new Counter();
while (true) {
// 每次增加隨機值
counter.add(random.nextInt(10));
Thread.sleep(1000);
}
}
}
Btrace測試
運行上訴測試用例
jvisualvm中找到對應的進程id-->Trace application...-->分別進行相應的測試
獲取add()方法參數值和返回值。
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
//獲取add()方法參數值和返回值。
@BTrace
public class TracingScript {
/* put your code here */
@OnMethod(
clazz="com.vmtools.Counter",
method="add",
location=@Location(Kind.RETURN)
)
public static void func(
int a,
@Return int result) {
println("trace: =======================");
jstack();
println(strcat("a:", str(a)));
println(strcat("result:", str(result)));
}
}
定時獲取Counter類的屬性值totalCount
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
//定時獲取Counter類的屬性值totalCount。
@BTrace
public class TracingScript {
private static Object totalCount=0;
/* put your code here */
@OnMethod(
clazz="com.vmtools.Counter",
method="add",
location=@Location(Kind.RETURN)
)
public static void func(@Self com.vmtools.Counter counter) {
totalCount = get(field("com.vmtools.Counter", "totalCount"), counter);
}
@OnTimer(2000)
public static void print(){
println(" ====== ");
println(strcat("totalCount: ",str(totalCount)));
}
}
獲取add方法執行時間
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
//獲取add方法執行時間
@BTrace
public class TracingScript {
@TLS private static long startTime = 0;
/* put your code here */
@OnMethod(
clazz="com.vmtools.Counter",
method="add"
)
public static void func(@Self com.vmtools.Counter counter) {
startTime = timeNanos();
}
@OnMethod(
clazz="com.vmtools.Counter",
method="add",
location=@Location(Kind.RETURN)
)
public static void endExecute(@Duration long duration){
long time = timeNanos() - startTime;
println(strcat("execute time(nanos): ", str(time)));
println(strcat("duration(nanos): ", str(duration)));
}
}
參考文檔:
