為什么要有Btrace?
生產(chǎn)環(huán)境系統(tǒng)發(fā)生問(wèn)題時(shí),定位問(wèn)題需要獲取系統(tǒng)運(yùn)行時(shí)的相關(guān)數(shù)據(jù),如方法參數(shù)、返回值、全局變量、堆棧信息等。為了獲取這些數(shù)據(jù),需要修改代碼,將數(shù)據(jù)輸出到日志文件,再發(fā)布到生產(chǎn)環(huán)境。這種方式,一方面將增大定位問(wèn)題的成本和周期,對(duì)于緊急問(wèn)題無(wú)法做到及時(shí)定位及解決;另一方面重新部署后環(huán)境很大程度上已被破壞,很難重現(xiàn)問(wèn)題。所以有一款可以在不重啟jvm的情況下,調(diào)試線上性能問(wèn)題的工具無(wú)疑是雪中送炭。幸運(yùn)的是,Btrace就是這樣一個(gè)工具。
Btrace使用到的技術(shù)有: Java Compiler API;?Annotation Processing;?Java Agent;?ASM 4;?Attach API;?jvmstat;?JMX
如何使用?
1. 安裝
最新github地址 https://github.com/btraceio/btrace/releases/tag/v1.3.9,之前的kenai已經(jīng)不能使用了。windows下直接解壓.zip包,linux下使用.tgz文件(注意:要配置環(huán)境變量)
此外,jvisualvm也可以集成btrace.去可用插件選型安裝即可。
如果安裝不成功,則可以進(jìn)行離線安裝(已下載插件-添加插件即可,可能還需要安裝一些btrace依賴包,自行下載),jvisualvm插件地址:https://visualvm.github.io/pluginscenters.html

2. 入門示例

格式: btrace pid *.java
3. 編寫(xiě)java腳本
常用的幾個(gè)注解
A、方法上的注解OnMethod
clazz: 類名稱,可以是全稱,也可以是正則表達(dá)式(也可以是正則表達(dá)式(表達(dá)式必須寫(xiě)在"http://"中, 比如"/java\.awt\..+/").)
例如: clazz="+java.lang.ClassLoader"--》實(shí)現(xiàn)ClassLoader接口的類;
method:方法名稱,
location:例如location=@Location(Kind.RETURN)是一個(gè)枚舉值
例如:
@OnMethod(
clazz="/java\.io\..Input./",
method="/read.*/"
)
B 、方法上的注解OnTimer 用來(lái)指定時(shí)長(zhǎng)(ms)執(zhí)行一次trace. 時(shí)長(zhǎng)通過(guò)"value"屬性指定。例如 @OnTimer(4000)
C、 其他方法上的注解
OnError 當(dāng)trace代碼拋異常時(shí)該注解的方法會(huì)被執(zhí)行. 如果同一個(gè)trace腳本中其他方法拋異常, 該注解方法也會(huì)被執(zhí)行.
OnExit 當(dāng)trace方法調(diào)用內(nèi)置exit(int)方法(用來(lái)結(jié)束整個(gè)trace程序)時(shí), 該注解的方法會(huì)被執(zhí)行. 參考自帶例子ProbeExit.java.
OnEvent 用來(lái)截獲"外部"btrace client觸發(fā)的事件, 比如按Ctrl-C 中斷btrace執(zhí)行時(shí)將執(zhí)行使用了該注解的方法, 該注解的value值為具體事件名稱.
OnLowMemory 當(dāng)內(nèi)存超過(guò)某個(gè)設(shè)定值將觸發(fā)該注解的方法, 具體參考MemAlerter.java
D、參數(shù)上的注解:Self 用來(lái)指定被trace方法的this
E、參數(shù)上的注解:Return 用來(lái)指定被trace方法的返回值
F、參數(shù)上的注解:ProbeClassName 和ProbeMethodName 被
trace的類名稱和方法名稱
G、參數(shù):.TargetInstance (since 1.1)
用來(lái)指定被trace方法內(nèi)部被調(diào)用到的實(shí)例
H、參數(shù):TargetMethodOrField (since 1.1)
用來(lái)指定被trace方法內(nèi)部被調(diào)用的方法名, 可參考例子AllCalls1.java 合 AllCalls2.java
I、屬性上的注解:TLS 將一個(gè)腳本變量與一個(gè)ThreadLocal變量關(guān)聯(lián)
常用的幾種腳本
- A、查看一個(gè)方法的入?yún)⒑头祷刂?/li>
- B、查看一個(gè)方法執(zhí)行耗時(shí)
- C、誰(shuí)調(diào)用這個(gè)方法
- D、代碼中的特定行有沒(méi)有被調(diào)用
代碼備注:
獲取一個(gè)類中方法的返回值、響應(yīng)時(shí)間以及調(diào)用情況
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
@BTrace
public class BtraceDemo {
@TLS
private static long beginTime;
@OnMethod(
clazz="完整類名",
method="getXXX"
)
public static void traceMethodBegin(){
beginTime = timeMillis();
}
@OnMethod(
clazz="完整類名(com.xx.xx)",
method="getXXX",
location=@Location(Kind.RETURN)
)
public static void traceMethdReturn(
@Return String result,
@ProbeClassName String clazzName,
@ProbeMethodName String methodName){
println("===========================================================================");
println(strcat(strcat(clazzName, "."), methodName));
println(strcat("Time taken : ", str(timeMillis() - beginTime)));
println("java thread method trace:---------------------------------------------------");
jstack();
println("----------------------------------------------------------------------------");
println(strcat("Reuslt :",str(result)));
println("============================================================================");
}
}
更多示例
Calculator類的add方法每隔5秒對(duì)a、b兩個(gè)數(shù)進(jìn)行相加,代碼如下。
public class Calculator {
private int c = 1;
public int add(int a, int b) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + b;
}
}
BTraceDemo調(diào)用Calculator的add方法對(duì)兩個(gè)隨機(jī)數(shù)進(jìn)行相加,代碼如下。
public class BTraceDemo {
public static void main(String[] args) {
Calculator calculator = new Calculator();
Random random = new Random();
while (true) {
System.out.println(calculator.add(random.nextInt(10), random.nextInt(10)));
}
}
}
1)BTraceTest則是相應(yīng)的追蹤腳本,代碼如下。
@BTrace
public class BTraceTest {
private static long count;
static{
println("---------------------------JVM properties:---------------------------");
printVmArguments();
println("---------------------------System properties:------------------------");
printProperties();
println("---------------------------OS properties:----------------------------");
printEnv();
exit();
}
@OnMethod(
clazz = "Calculator",
method = "add",
location = @Location(Kind.RETURN)
)
public static void trace1(int a, int b, @Return int sum) {
println("trace1:a=" + a + ",b=" + b + ",sum=" + sum);
}
}
運(yùn)行如下命令:
btrace 11308 /Users/wlxs/java/BTraceTest.java
11308是BTraceDemo的進(jìn)程ID,靜態(tài)塊中的輸出結(jié)果就不展示了。trace1方法實(shí)現(xiàn)對(duì)Calculator類的add方法的入?yún)⒑头祷刂颠M(jìn)行追蹤,結(jié)果如下。
trace1:a=2,b=6,sum=8
2)為了節(jié)省篇幅,下面都將只列出各個(gè)追蹤的方法,trace2追蹤C(jī)alculator類的add方法執(zhí)行時(shí)間,默認(rèn)時(shí)間單位是納秒。
@OnMethod(
clazz = "Calculator",
method = "add",
location = @Location(Kind.RETURN)
)
public static void trace2(@Duration long duration) {
println(strcat("duration(nanos): ", str(duration)));
println(strcat("duration(s): ", str(duration / 1000000000)));
}
結(jié)果如下。
duration(nanos): 5004187000
duration(s): 5
3)trace3追蹤C(jī)alculator類的add方法,并且追蹤add方法中的任何類的sleep方法,代碼如下。
@OnMethod(
clazz = "Calculator",
method = "add",
location = @Location(value = Kind.CALL, clazz = "/.*/", method = "sleep")
)
public static void trace3(@ProbeClassName String pcm, @ProbeMethodName String pmn,
@TargetInstance Object instance, @TargetMethodOrField String method) {
println(strcat("ProbeClassName: ", pcm));
println(strcat("ProbeMethodName: ", pmn));
println(strcat("TargetInstance: ", str(instance)));
println(strcat("TargetMethodOrField : ", str(method)));
println(strcat("count: ", str(++count)));
}
結(jié)果如下。
ProbeClassName: Calculator
ProbeMethodName: add
TargetInstance: null
TargetMethodOrField : sleep
count: 1
4)trace4每隔6秒打印一次count的值,代碼如下。
@OnTimer(6000)
public static void trace4() {
println(strcat("trace4:count: ", str(count)));
}
結(jié)果如下。
trace4:count: 1
5)trace5用于獲取Calculator類的c屬性的值,代碼如下。
@OnMethod(
clazz = "Calculator",
method = "add",
location = @Location(Kind.RETURN)
)
public static void trace5(@Self Object calculator) {
println(get(field("Calculator", "c"), calculator));
}
6)traceMemory每隔4秒打印一次印堆和非堆內(nèi)存信息,代碼如下。
@OnTimer(4000)
public static void traceMemory() {
println("heap:");
println(heapUsage());
println("no-heap:");
println(nonHeapUsage());
}
結(jié)果如下。
heap:
init = 10485760(10240K) used = 4430576(4326K) committed = 9961472(9728K) max = 9961472(9728K)
no-heap:
init = 24576000(24000K) used = 7813992(7630K) committed = 24576000(24000K) max = 136314880(133120K)
7)trace6每隔4秒檢測(cè)是否有死鎖產(chǎn)生,并打印產(chǎn)生死鎖的相關(guān)類信息、對(duì)應(yīng)的代碼行、線程信息,代碼如下。
@OnTimer(4000)
public static void trace6() {
deadlocks();
}
參考文章:http://iamzhongyong.iteye.com/blog/1729743
http://www.itdecent.cn/p/1b52561e3848