BTrace使用

BTrace簡介

首先***BTrace就是爲了解決線上問題而存在的***html

舉一個例子,由於設計問題,咱們生產環境有一個Map的大小超過了16M,這個Map是要一次寫入MongoDB數據庫中的,可是MongoDB數據庫的一個文檔大小最多不超過16M,超過就沒有辦法寫入,這個問題怎麼解決?java

首先,不能重啓,重啓數據就丟了。堆dump,而後去解析dump數據?今天咱們來介紹一些使用BTrace解決這個問題。git

BTrace能夠獲取程序運行時的數據信息,如方法參數、返回值、全局變量、堆棧信息等github

咱們先來看一下使用BTrace怎麼解決上面的那個問題,瞭解一下BTrace怎麼使用。若是有地方暫時不清楚的也沒有關係,能夠看後面的一些說明。正則表達式

BTrace 導出map數據

首先到BTrace下載去下載BTrace的release版。數據庫

解壓到文件,大概像是下面這樣:緩存

根目錄

bin目錄

測試業務類

如今假設咱們的業務類是這樣子的: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");
    }
}

BTrace腳本

咱們想要獲取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這種方式就能夠了。

ClassNotFound

若是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目錄大概就是想下面這樣了:

new_bin

而後

  1. 啓動咱們的測試業務類BusinessMap

  2. 執行jps命令找到BusinessMap的pid [jps]

  3. 使用下面的命令執行腳本(2箇中的任一個)

btrace 5244 BusinessMapTrace.java
btrace -o D:\ptool\btrace\bin\map.txt 5244 BusinessMapTrace.java

-o是參數是輸出到指定文件,注意要使用絕對路徑,使用相對路徑並不會出如今腳本目錄下。

exe_btrace

若是你遇到下面的錯誤,不要着急,把腳本中的中文註釋刪除了就能夠了。

gbk_error

若是出現什麼btrace-libs找不到就在build下面新建一個btrace-libs把build下的jar包拷貝到btrace-libs目錄下就能夠了。

如今再看上面的問題是否是簡單多了,這裏咱們就很少介紹例子了,你能夠在BTrace實例這裏看到更多的例子,或者在github下載的BTrace的release版本中的sample中有不少使用的例子,在UserGuide中有記錄每個例子測試的是什麼。

下面主要介紹BTrace中經常使用的註解和方法。

@OnMethod註解

@OnMethod註解經常使用的屬性:

  1. "clazz"屬性:用來指定目標類名
  2. "method"屬性:用來指定被trace的方法
  3. "location"屬性:用來指定攔截時機
  4. "type"屬性:用來指定方法簽名(重載的時候,只有方法名不行)如:type="int (int, int)"

location後面單獨講,先說"clazz"和"method"

  1. 使用全限定名
clazz="cn.freemethod.btra.BtraceMap"
method="sayHello"

注意:靜態內部類的寫法,是在類與內部類之間加上"$"

  1. 使用正則表達式 方法和類均可以使用正則表達式
clazz="/java\\.lang\\..*/"
method="/.*/"
  1. 使用接口 接口在前面添加"+"代表是一個接口就可了
clazz="+xxx.xxx.Interface"
  1. 使用註解 類和方法均可以使用註解,在前面加"@"就能夠了
clazz="@xxx.xxx.Annotation"
method="@xxx.xxx.Annotation"
  1. 構造方法 構造方法指定method="<init>"就能夠了

注意正則表達式須要寫在兩個 "/",正則表達式的範圍要儘量的小,否則會很是慢

注意:@OnMethod都是註解的public static void方法

@OnTimer註解

定時觸發Trace,時間能夠指定,單位爲毫秒

@OnError註解

當trace代碼拋異常或者錯誤時,該註解的方法會被執行.若是同一個trace腳本中其餘方法拋異常,該註解方法也會被執行

@OnExit

當trace方法調用內置exit(int)方法(用來結束整個trace程序),該註解的方法會被執行

@OnEvent

用來截獲"外部"btrace client觸發的事件

@OnLowMemory

當內存超過某個設定值將觸發該註解的方法

@OnProbe

使用外部文件XML來定義trace方法以及具體的位置

@TLS

定義ThreadLocal的共享變量

@Export

該註解的靜態屬性主要用來與jvmstat計數器作關聯 例若有:

@Export private static long count;

其餘腳本能夠經過

Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count")

這種方式引用

@Location註解

定義Btrace對方法的攔截位置 經過@Location註解指定 默認爲Kind.ENTRY

  1. Kind.ENTRY 在進入方法時,調用Btrace腳本

  2. Kind.RETURN 方法執行完時,調用Btrace腳本,只有把攔截位置定義爲Kind.RETURN,才能獲取方法的返回結果@Return和執行時間@Duration

  3. Kind.CALL 分析方法中調用其它方法的執行狀況

  4. Kind.LINE 經過設置line,能夠監控代碼是否執行到指定的位置

  5. Kind.ERROR 異常未捕獲被拋出方法以外

  6. Kind.THROW 異常拋出

  7. Kind.CATCH 異常被捕獲

參數註解

@Self用來指定被trace方法的this

@Return用來指定被trace方法的返回值

@ProbeClassName用來指定被trace的類名

@ProbeMethodName用來指定被trace的方法名

@TargetInstance用來指定被trace方法內部被調用到的實例

@TargetMethodOrField用來指定被trace方法內部被調用的方法名

BTrace方法

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;
  1. 獲取當前線程名稱
BTraceUtils.Threads.name(BTraceUtils.currentThread())
  1. 獲取Hash code
BTraceUtils.identityHashCode()
  1. 獲取對象的類名稱
BTraceUtils.Reflective.name(clazz)
  1. 返回類
BTraceUtils.classOf(obj)
  1. 打印信息
BTraceUtils.print()
BTraceUtils.println()
BTraceUtils.printArray()
printVmArguments()
printProperties()
printEnv()
printFields(obj)
  1. 獲取值
field("java.lang.Thread", "name")
field(classForName("xxx.xxx.ClassName", contextClassLoader()),"fieldName")
get(field, obj)
getBoolean()
getInt()
  1. 系統相關
exit()//退出BTrace
heapUsage()//堆使用狀況
nonHeapUsage()//非堆使用狀況
jstack()//堆棧信息
Sys.Memory.dumpHeap("data.bin")//堆dump

限制

  1. 不能建立對象
  2. 不能拋出或者捕獲異常
  3. 不能用synchronized關鍵字
  4. 不能對目標程序中的instace或者static變量
  5. 不能調用目標程序的instance或者static方法
  6. 腳本的field、method都必須是static的
  7. 腳本不能包括outer,inner,nested class
  8. 腳本中不能有循環,不能繼承任何類,任何接口與assert語句

BTrace參數

-cp

btrace -cp .:xxx.jar $pid HelloWorld.java

指定classpath,若是腳本文件HelloWorld.java有用到xxx.jar包

-o

btrace -o data.txt $pid HelloWorld.java

輸出到文件,好比咱們須要獲取緩存數據,這個緩存數據比較大,而且想使用這些,就可使用-o參數

-u

能夠用-u 運行在unsafe mode來規避前面提到的限制,但不推薦

參考

GitHub BTrace

BTrace簡介及使用

Btrace入門到熟練小工徹底指南

BTrace實例

相關文章
相關標籤/搜索