java 調(diào)優(yōu)之 Btrace

為什么要有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

Paste_Image.png

2. 入門示例

Paste_Image.png

格式: 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,626評(píng)論 18 399
  • 。為愛(ài)癡狂。 我從春天走來(lái) 你在秋天說(shuō)要分開(kāi) 說(shuō)好不為你憂傷 但心情怎會(huì)無(wú)恙 為何總是這樣 在我心中深藏著你 想要...
    愛(ài)小月閱讀 198評(píng)論 0 0
  • 在昨天的《如何閱讀一本書(shū)》前半本分享中,提到分析閱讀,也就是閱讀層次中的第三個(gè)層次。因篇幅有限,沒(méi)展開(kāi)講。...
    何沐一閱讀 616評(píng)論 0 4

友情鏈接更多精彩內(nèi)容