版本 1.2 (20101020)
BTrace 是一個可靠的,用來動態跟蹤Java程序的工具。它通過動態對運行中的Java程序進行字節碼生成來工作。BTrace會對運行中的Java程序的類插入一些跟蹤操作 來對被跟蹤的程序進行熱替換。
BTrace 名詞
探測點 (Probe Point)
就是一系列的跟蹤語句被執行的“地方”或者“事件”。探測點就是我們想要執行一些跟蹤語句的地方或者事件。
跟蹤動作或簡稱動作 (Trace Actions)
就是那些當探測點被觸發時所執行的跟蹤語句。
動作方法
BTrace的跟蹤語句是必須定義在一個類的某個靜態方法裏的,這個靜態方法就叫“動作”方法。
BTrace 程序結構
一個BTrace程序就是一個普通的Java類,這個類至少有一個這樣的方法:
public static void ***
其次這個方法還需要加上BTrace相關的註解。這些註解用來指明被跟蹤程序的“位置”(也就是前面提到的探測點)。跟蹤動作需要在這個靜態方法的方法體裏指定。這些(注意,可以有多個)靜態方法就是所謂的“動作”方法。
BTrace的限制
爲了保證跟蹤動作是“只讀”的(也就是這些動作不可以修改被跟蹤程序的狀態)和有限度的(比如在固定時間裏結束)。一個BTrace程序只允許完成一些指定的動作。下面是BTrace一些不可以完成的事情:
- 不能創建新的對象
- 不能創建新的數組
- 不能拋出異常
- 不能捕獲異常
- 不能進行任何的實例函數或者靜態函數 -- 只有com.sun.btrace.BTraceUtils類中的靜態函數或者BTrace程序自己聲明的
- 函數纔可以被BTrace調用
- 對1.2版本以前的程序,不能由實例級別的field和函數。只有靜態公開的並且無返回值的函數才允許在BTrace類中使用。所有
的field必須是靜態的。
- 不可以在目標程序的類,或者對象的靜態或者實例級別的field進行賦值。但是,BTrace自身的類是可以給它的靜態field進行賦值的。
- (也就是意味着跟蹤的狀態時可以更改的)
- 不能有outer,inner,嵌套的或者本地類。
- 不能有同步代碼塊或者同步的函數
- 不能有循環語句(for,while, do..while)
- 不能繼承其它類(父類只能是java.lang.Object)
- 不能實現接口
- 不能包含斷言(assert)語句
- can NOT use class literals (這個我也沒搞明白是啥意思)
一個簡單的BTrace程序(適用於版本1.2)
一個簡單的Brace程序 (適用於1.2以前的版本)
以上的程序都可以對一個正在運行的Java進行跟蹤。這個程序會在目標程序調用Thread.start()函數來啓動一個線程時打印出“about to start a thread!".還有其他一些有趣的探測點,比如,我們可以一個函數返回的時候插入一段探測動作,或者是一個函數碰到異常時也插入一段,在函數裏用到的field,對象和數組的創建,代碼行號等等。參考
@OnMethod和其它註解來了解更多細節。
運行BTrace的步驟
- 找到你要跟蹤的Java進程的ID。可以用JDK自帶的jps程序找到你要的進程id。
- 編寫一段BTrace程序 -- 你也可以修改我們的樣例程序
- 運行btrace工具 (到官方網站下載最新的壓縮包解壓,在bin目錄就有這個文件,注意如果你是在windows環境下,那麼這個文件的名字應該是btrace.bat):
btrace <pid> <btrace-script>
注意: BTrace只支持JDK 6以上的環境
BTrace 命令行
BTrace 通過使用下面的btrace命令行工具來運行:
btrace [-I <include-path>] [-p <port>] [-cp <classpath>] <pid> <btrace-script> [<args>]
其中各個參數的含義如下:
include-path : 是一些用來查找頭文件的目錄。BTrace
- port: BTrace代理程序所偵聽的端口,這是可選的選項。默認是2020
- classpath: 是一些用來查找jar文件的目錄。默認是".",也就是當前目錄
- btrace-script: 就是跟蹤程序本身。如果這是個java文件,那麼在執行前會進行編譯。否則就被認爲是已經編譯好的
- 程序(比如可能是個.class文件),而直接運行
- args: 這是傳遞給BTrace程序的參數。BTrace程序可以通過內置的$符號來引用這些參數,$length是這些參數的個數。
編譯BTrace腳本
使用btracec腳本,我們可以對BTrace程序進行編譯。btracec就是類似javac那樣的程序,輸入是一個BTrace程序,輸出時一個.class文件。
btracec [-I <include-path>] [-cp <classpath>] [-d <directory>] <one-or-more-BTrace-.java-files>
各個參數的含義如下:
- include-path
- classpath:
- directory:
使用BTrace代理來啓動一個目標程序
到目前爲止,我們已經知道如何跟蹤一個正在運行的Java程序。我們甚至可以通過BTrace代理來啓動目標程序。如果你想跟蹤目標程序開始的時候作了什麼事情,你就需要通過BTrace代理來啓動它,並制定對應的跟蹤腳本。下面的命令就是叫你如何作到這點的。需要注意的是:這裏制定的跟蹤腳本必須是已經編譯好的(就是.class文件)。
java -javaagent:btrace-agent.jar=script=<pre-compiled-btrace-script1>[,<pre-compiled-btrace-script1>]* <MainClass> <AppArguments>
以這種方式啓動的目標程序,會把跟蹤輸出到當前目錄下一個叫作<btrace-class-file-name>.btrace的文件中。如果你不想這個目標程序給其他的遠程BTrace客戶端使用,那麼可以指定noServer=true這個參數給BTrace代理。BTrace的發佈目錄下有個叫btracer的腳本就是專門作上面的事情的:
btracer <pre-compiled-btrace.class> <application-main-class> <application-args>
支持的參數
- bootClassPath - 啓動時用到的classpath
- systemClassPath - 系統classpath
- debug - 是否輸出詳細的調試日誌(true則輸出,false則不輸出)
- unsafe - 是否不檢查是否違反了btrace限制 (true則不檢查,false則檢查)
- dumpClasses - 是否把二進制碼dump到文件中(true/false)
- dumpDir - dump出來文件放到這個目錄下
- stdout - 是否把btrace輸出重定向到標準輸出(true/false)
- probeDescPath - 存放探測點描述文件的路徑
- script - 腳本的路徑,當代理啓動時會運行這個腳本
- scriptdir - 腳本所在的目錄,當代理啓動時會運行這個目錄下的腳本
- scriptOutputFile - 輸出文件的路徑,btrace代理會把輸出寫到這個文件中
重要的系統屬性
btrace.agentname- 用來區分同一臺機器上運行着的不同的btrace代理。
BTrace的註解
方法註解
@com.sun.btrace.annotations.OnMethod 這個註解可用來指定目標類,目標方法,以及目標方法裏的”位置“。加了這個註解後的操作方法會在對應的方法運行到指定的”地點“時被執行。這這個註解中,目標類用”clazz“屬性來指定,而目標方法用”method“屬性來指定。"clazz"可以是類的全路徑(比如java.awt.Component或者用兩個反斜槓中間的正則表達式,參考例子NewComponent.java和Classload.java來看它們的用法,正則表達式可以匹配0個或多個目標類,這個時候多個類都會被進行動態指令更換。如/java\\.awt\\.+/匹配java.awt包下的所有類)。方法名也可以用這樣的正則表達式
來匹配零個或者多個多個方法。參考例子MultiClass.java來查看用法。 還有一種方法來指定跟蹤類和函數。被跟蹤的類和函數可以用註解來指定。比如,如果"clazz"屬性是@javax.jws.Webservice.那麼BTrace會會把所有註解是這個的函數都進行動態指令更換。類似地,方法級別的註解也可以用來執行方法。參看例子WebServiceTracker.java來了解如何使用。可以把正則表達式和註解放在一起用,比如@/com\\.acme\\..+/可以匹配任何類,只要這個類的註解能跟那段正則表達式匹配即可。可以通過指定父類來匹配多個類名,比如+java.lang.Runnable就可以匹配所有實現了java.lang.Runnable這個接口的類。參考例子SubtypeTracer.java來看它的用法。
@com.sun.btrace.annotations.OnEvent 這個註解用來跟蹤函數與"外部”的事件關聯起來。當BTrace客戶端發送了一個“事件”後,這個註解裏的操作就會被執行。客戶端發送的事件可能是由用戶觸發的(比如按下Ctrl-C)。事件的名字是個字符串,這樣跟蹤操作就只會在對應的事件觸發後被執行。到目標爲止,BTrace命令行客戶端會在用戶按下Ctrl-C後發送事件,參考例子HistoOnEvent.java來了解用法。
@com.sun.btrace.annotations.OnProbe 這個註解可以用來避免使用BTrace腳本的內部類。@OnProbe探測點被映射到一個或多個@OnMethod上。目前這個映射是通過一個XML探測描述文件類指定的(這個文件會被BTrace代理所使用)。參考例子SocketTracker1.java和對應的描述文件java.net.socket.xml.當運行這個例子時,xml文件需要放在目標JVM所有運行的目錄下(或者修改btracer.bat中的probeDescPath選項來指向任意的xml文件)。
參數相關的註解
1.2版本以後,有一個布爾型的參數fqn可以用來執行是否獲取函數的全名稱
1.2版本以後,有一個布爾型的參數fqn可以用來執行是否獲取函數得到全名稱
無註解的參數
沒有註解的BTrace探測函數參數是用來作簽名匹配的,因爲他們必須必須在固定的位置上出現。然而,它們可以和其他的註解的參數進行交換。如果一個參數的類型是*AnyType[]*,它就會“吃”掉所所有剩下的參數。沒有註解的參數的具體含義與他們所在的位置有關:
- Kind.ENTRY, Kind.RETURN- 探測函數的參數
- Kind.THROW - the thrown exception
- Kind.ARRAY_SET, Kind.ARRAY_GET - 數組的索引
- Kind.CATCH - 被捕獲的異常
- Kind.FIELD_SET - field的值
- Kind.LINE - 源代碼行號
- Kind.NEW - 類名
- Kind.ERROR - 拋出去的異常
字段相關的註解
@com.sun.btrace.annotations.Export BTrace字段使用這個註解來說明它已經被映射到一個jvmstat計數器上。使用這個註解,BTrace程序可以把跟蹤計數器暴露給外部的jvmstat客戶端(比如jstat)。參考例子ThreadCounter.java
@com.sun.btrace.annotations.Property這個註解可以把一個字段標識爲一個MBean屬性。如果一個BTrace類至少有一個靜態的字段使用了這個註解。那麼一個MBean就會被創建並且註冊到平臺MBean服務器上。JMX客戶端比如VisualVM,jconsole可以訪問這個字段來查看BTrace的MBean。在把BTrace附加到目標程序上後,你可以把VisualVM或者jconsole也附加到同一個目標程序上來查看剛創建好的MBean屬性。通過VisualVM或者jconsole,你可以通過MBeans
tab頁來查看BTrace相關的域,然後查看它們的值。參考例子ThreadCounterBean.java 和HistogramBean.java來了解用法
@com.sun.btrace.annotations.TLS BTrace字段使用這個註解來說明它自己是一個線程本地字段(thread
local field).注意你只能在@OnMethod註解後的函數裏訪問這樣的字段。每個Java線程都有一個這個字段的拷貝。爲了讓這樣的方式能夠工作,這個字段的類型只能是immutable(比如原始類型) 或者是cloneable (實現了Cloneable接口並且覆蓋了clone()函數)的。這些線程本地字段可以被BTrace程序用來識別它是否在同一個線程裏執行了多個探測操作。參考例子OnThrow.java和WebServiceTracker.java
類相關的註解
DTrace集成
現在很少人用Solaris, 所以這段就略過啦,吼吼。
BTrace例子
例子的簡短說明: