首先***BTrace就是爲了解決線上問題而存在的***html
舉一個例子,由於設計問題,咱們生產環境有一個Map的大小超過了16M,這個Map是要一次寫入MongoDB數據庫中的,可是MongoDB數據庫的一個文檔大小最多不超過16M,超過就沒有辦法寫入,這個問題怎麼解決?java
首先,不能重啓,重啓數據就丟了。堆dump,而後去解析dump數據?今天咱們來介紹一些使用BTrace解決這個問題。git
BTrace能夠獲取程序運行時的數據信息,如方法參數、返回值、全局變量、堆棧信息等github
咱們先來看一下使用BTrace怎麼解決上面的那個問題,瞭解一下BTrace怎麼使用。若是有地方暫時不清楚的也沒有關係,能夠看後面的一些說明。正則表達式
首先到BTrace下載去下載BTrace的release版。數據庫
解壓到文件,大概像是下面這樣:緩存
如今假設咱們的業務類是這樣子的:jvm
package cn.freemethod.business; import java.util.HashMap; import java.util.Scanner; public class BusinessMap { private static HashMap<Integer,Integer> map = new HashMap<>(); static { map.put(1,1); map.put(2,2); map.put(3,3); map.put(4,4); } public static void main(String[] args) throws InterruptedException { final Scanner scanner = new Scanner(System.in); scanner.nextLine(); // final BusinessMap bm = new BusinessMap(); // for (int i = 0; i < 1000; i++) { // TimeUnit.SECONDS.sleep(5); // bm.business(); // } } public void business(){ System.out.println("business"); } }
咱們想要獲取map中的數據,怎麼辦呢?咱們就能夠寫一個BTrace腳本(java和class文件均可以),像下面這樣子:ide
import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.OnMethod; import com.sun.btrace.annotations.OnTimer; import com.sun.btrace.annotations.Self; import java.lang.reflect.Field; import static com.sun.btrace.BTraceUtils.Reflective; import static com.sun.btrace.BTraceUtils.Reflective.classForName; import static com.sun.btrace.BTraceUtils.Reflective.contextClassLoader; import static com.sun.btrace.BTraceUtils.println; @BTrace public class BusinessMapTrace { @OnMethod( clazz="cn.freemethod.business.BusinessMap", method="business" ) public static void getMap(@Self Object bm) { //若是map是實例變量 Field mapField = Reflective.field("cn.freemethod.business.BusinessMap", "map"); println(Reflective.get(mapField,bm)); } //一分鐘執行一次 @OnTimer(1000*60) public static void timeGetMap(){ //若是map是靜態變量 Class clazz = classForName("cn.freemethod.business.BusinessMap", contextClassLoader()); Field mapField = Reflective.field(clazz, "map"); println(Reflective.get(mapField)); } }
由於map是靜態變量,全部能夠直接使用@OnTimer註解的這個方法。@OnTimer註解是固定多少時間執行一次,單位是毫秒@OnTimer(1000*60)就是一分鐘執行一次。測試
注意這裏咱們是使用:
Class clazz = classForName("cn.freemethod.business.BusinessMap", contextClassLoader()); Field mapField = Reflective.field(clazz, "map");
這種方式獲取到Field的,和上面的@OnMethodz中的直接使用類全限定名是不同的。有些資料說是JDK自帶的就可使用像:
Field mapField = Reflective.field("cn.freemethod.business.BusinessMap", "map");
這種使用全限定名的方式,其餘的類就使用classForName這種方式,感受是有侷限性的啊,上面一個我沒有使用classForName這種方式也沒有問題的。
就是儘可能使用classForName這種方式吧,若是使用全限定名字符串這種方式出現下面的錯誤就改爲classForName這種方式就能夠了。
若是map是實例變量那怎麼辦呢,就使用上一個方法嘛。
@OnMethod(clazz="cn.freemethod.business.BusinessMap", method="business")
咱們知道Java的AOP中攔截的通常是方法,BTrace也同樣。上面的OnMethod就表示當cn.freemethod.business.BusinessMap這個類執行business方法的時候執行。可是是何時執行呢,進入方法的時候?方法返回的時候?其實還有一個location屬性來控制,默認是進入方法的時候執行。後面有介紹location內容的,這裏不詳細說了。
@Self註解是把調用business方法的instance注入,就是獲取this。由於是實例變量全部咱們要經過實例來獲取屬性。全部經過@Self把實例注入進來。
而後就能夠經過BTrace提供的field方法獲取Field,經過get方法獲取Field實例了。經過println方法來打印map對象了。
腳本咱們有了,可是怎麼使用呢?下面咱們就簡單的說一下腳本怎麼使用。首先咱們把腳本拷貝到bin目錄下(主要是我懶,不想加腳本路徑),bin目錄大概就是想下面這樣了:
而後
啓動咱們的測試業務類BusinessMap
執行jps命令找到BusinessMap的pid [jps]
使用下面的命令執行腳本(2箇中的任一個)
btrace 5244 BusinessMapTrace.java btrace -o D:\ptool\btrace\bin\map.txt 5244 BusinessMapTrace.java
-o是參數是輸出到指定文件,注意要使用絕對路徑,使用相對路徑並不會出如今腳本目錄下。
若是你遇到下面的錯誤,不要着急,把腳本中的中文註釋刪除了就能夠了。
若是出現什麼btrace-libs找不到就在build下面新建一個btrace-libs把build下的jar包拷貝到btrace-libs目錄下就能夠了。
如今再看上面的問題是否是簡單多了,這裏咱們就很少介紹例子了,你能夠在BTrace實例這裏看到更多的例子,或者在github下載的BTrace的release版本中的sample中有不少使用的例子,在UserGuide中有記錄每個例子測試的是什麼。
下面主要介紹BTrace中經常使用的註解和方法。
@OnMethod註解經常使用的屬性:
location後面單獨講,先說"clazz"和"method"
clazz="cn.freemethod.btra.BtraceMap" method="sayHello"
注意:靜態內部類的寫法,是在類與內部類之間加上"$"
clazz="/java\\.lang\\..*/" method="/.*/"
clazz="+xxx.xxx.Interface"
clazz="@xxx.xxx.Annotation" method="@xxx.xxx.Annotation"
注意正則表達式須要寫在兩個 "/",正則表達式的範圍要儘量的小,否則會很是慢
注意:@OnMethod都是註解的public static void方法
定時觸發Trace,時間能夠指定,單位爲毫秒
當trace代碼拋異常或者錯誤時,該註解的方法會被執行.若是同一個trace腳本中其餘方法拋異常,該註解方法也會被執行
當trace方法調用內置exit(int)方法(用來結束整個trace程序),該註解的方法會被執行
用來截獲"外部"btrace client觸發的事件
當內存超過某個設定值將觸發該註解的方法
使用外部文件XML來定義trace方法以及具體的位置
定義ThreadLocal的共享變量
該註解的靜態屬性主要用來與jvmstat計數器作關聯 例若有:
@Export private static long count;
其餘腳本能夠經過
Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count")
這種方式引用
定義Btrace對方法的攔截位置 經過@Location註解指定 默認爲Kind.ENTRY
Kind.ENTRY 在進入方法時,調用Btrace腳本
Kind.RETURN 方法執行完時,調用Btrace腳本,只有把攔截位置定義爲Kind.RETURN,才能獲取方法的返回結果@Return和執行時間@Duration
Kind.CALL 分析方法中調用其它方法的執行狀況
Kind.LINE 經過設置line,能夠監控代碼是否執行到指定的位置
Kind.ERROR 異常未捕獲被拋出方法以外
Kind.THROW 異常拋出
Kind.CATCH 異常被捕獲
@Self用來指定被trace方法的this
@Return用來指定被trace方法的返回值
@ProbeClassName用來指定被trace的類名
@ProbeMethodName用來指定被trace的方法名
@TargetInstance用來指定被trace方法內部被調用到的實例
@TargetMethodOrField用來指定被trace方法內部被調用的方法名
import static com.sun.btrace.BTraceUtils.exit; import static com.sun.btrace.BTraceUtils.field; import static com.sun.btrace.BTraceUtils.get; import static com.sun.btrace.BTraceUtils.jstack; import static com.sun.btrace.BTraceUtils.printEnv; import static com.sun.btrace.BTraceUtils.printFields; import static com.sun.btrace.BTraceUtils.printProperties; import static com.sun.btrace.BTraceUtils.printVmArguments; import static com.sun.btrace.BTraceUtils.println; import com.sun.btrace.BTraceUtils.Strings; import com.sun.btrace.BTraceUtils.Sys; import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.Duration; import com.sun.btrace.annotations.Kind; import com.sun.btrace.annotations.Location; import com.sun.btrace.annotations.OnMethod; import com.sun.btrace.annotations.Self;
BTraceUtils.Threads.name(BTraceUtils.currentThread())
BTraceUtils.identityHashCode()
BTraceUtils.Reflective.name(clazz)
BTraceUtils.classOf(obj)
BTraceUtils.print() BTraceUtils.println() BTraceUtils.printArray() printVmArguments() printProperties() printEnv() printFields(obj)
field("java.lang.Thread", "name") field(classForName("xxx.xxx.ClassName", contextClassLoader()),"fieldName") get(field, obj) getBoolean() getInt()
exit()//退出BTrace heapUsage()//堆使用狀況 nonHeapUsage()//非堆使用狀況 jstack()//堆棧信息 Sys.Memory.dumpHeap("data.bin")//堆dump
btrace -cp .:xxx.jar $pid HelloWorld.java
指定classpath,若是腳本文件HelloWorld.java有用到xxx.jar包
btrace -o data.txt $pid HelloWorld.java
輸出到文件,好比咱們須要獲取緩存數據,這個緩存數據比較大,而且想使用這些,就可使用-o參數
能夠用-u 運行在unsafe mode來規避前面提到的限制,但不推薦