java 调优之 Btrace

为什么要有Btrace?

生产环境系统发生问题时,定位问题需要获取系统运行时的相关数据,如方法参数、返回值、全局变量、堆栈信息等。为了获取这些数据,需要修改代码,将数据输出到日志文件,再发布到生产环境。这种方式,一方面将增大定位问题的成本和周期,对于紧急问题无法做到及时定位及解决;另一方面重新部署后环境很大程度上已被破坏,很难重现问题。所以有一款可以在不重启jvm的情况下,调试线上性能问题的工具无疑是雪中送炭。幸运的是,Btrace就是这样一个工具。
Btrace使用到的技术有: 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已经不能使用了。windows下直接解压.zip包,linux下使用.tgz文件(注意:要配置环境变量)
此外,jvisualvm也可以集成btrace.去可用插件选型安装即可。
如果安装不成功,则可以进行离线安装(已下载插件-添加插件即可,可能还需要安装一些btrace依赖包,自行下载),jvisualvm插件地址:https://visualvm.github.io/pluginscenters.html

Paste_Image.png

2. 入门示例

Paste_Image.png

格式: btrace pid *.java

3. 编写java脚本

常用的几个注解

A、方法上的注解OnMethod

clazz: 类名称,可以是全称,也可以是正则表达式(也可以是正则表达式(表达式必须写在"//"中, 比如"/java\.awt\..+/").)
例如: clazz="+java.lang.ClassLoader"--》实现ClassLoader接口的类;
method:方法名称,
location:例如location=@Location(Kind.RETURN)是一个枚举值
例如:
@OnMethod(
clazz="/java\.io\..Input./",
method="/read.*/"
)

B 、方法上的注解OnTimer 用来指定时长(ms)执行一次trace. 时长通过"value"属性指定。例如 @OnTimer(4000)
C、 其他方法上的注解

OnError 当trace代码抛异常时该注解的方法会被执行. 如果同一个trace脚本中其他方法抛异常, 该注解方法也会被执行.
OnExit 当trace方法调用内置exit(int)方法(用来结束整个trace程序)时, 该注解的方法会被执行. 参考自带例子ProbeExit.java.
OnEvent 用来截获"外部"btrace client触发的事件, 比如按Ctrl-C 中断btrace执行时将执行使用了该注解的方法, 该注解的value值为具体事件名称.
OnLowMemory 当内存超过某个设定值将触发该注解的方法, 具体参考MemAlerter.java

D、参数上的注解:Self 用来指定被trace方法的this
E、参数上的注解:Return 用来指定被trace方法的返回值
F、参数上的注解:ProbeClassName 和ProbeMethodName 被

trace的类名称和方法名称

G、参数:.TargetInstance (since 1.1)

用来指定被trace方法内部被调用到的实例

H、参数:TargetMethodOrField (since 1.1)

用来指定被trace方法内部被调用的方法名, 可参考例子AllCalls1.java 合 AllCalls2.java

I、属性上的注解:TLS 将一个脚本变量与一个ThreadLocal变量关联

常用的几种脚本
  • A、查看一个方法的入参和返回值
  • B、查看一个方法执行耗时
  • C、谁调用这个方法
  • D、代码中的特定行有没有被调用

代码备注:
获取一个类中方法的返回值、响应时间以及调用情况

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秒对a、b两个数进行相加,代码如下。

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调用Calculator的add方法对两个随机数进行相加,代码如下。

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则是相应的追踪脚本,代码如下。

@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);
    }
}

运行如下命令:

btrace 11308 /Users/wlxs/java/BTraceTest.java
11308是BTraceDemo的进程ID,静态块中的输出结果就不展示了。trace1方法实现对Calculator类的add方法的入参和返回值进行追踪,结果如下。

trace1:a=2,b=6,sum=8
2)为了节省篇幅,下面都将只列出各个追踪的方法,trace2追踪Calculator类的add方法执行时间,默认时间单位是纳秒。

   @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)));
    }

结果如下。

duration(nanos): 5004187000
duration(s): 5
3)trace3追踪Calculator类的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)));
    }

结果如下。

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)));
    }

结果如下。

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秒打印一次印堆和非堆内存信息,代码如下。

    @OnTimer(4000)
    public static void traceMemory() {
        println("heap:");
        println(heapUsage());
        println("no-heap:");
        println(nonHeapUsage());
    }

结果如下。

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秒检测是否有死锁产生,并打印产生死锁的相关类信息、对应的代码行、线程信息,代码如下。

@OnTimer(4000)
public static void trace6() {
    deadlocks();
}

参考文章:http://iamzhongyong.iteye.com/blog/1729743
http://www.jianshu.com/p/1b52561e3848

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,365评论 6 343
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,296评论 18 399
  • 。为爱痴狂。 我从春天走来 你在秋天说要分开 说好不为你忧伤 但心情怎会无恙 为何总是这样 在我心中深藏着你 想要...
    爱小月阅读 160评论 0 0
  • 在昨天的《如何阅读一本书》前半本分享中,提到分析阅读,也就是阅读层次中的第三个层次。因篇幅有限,没展开讲。...
    何沐一阅读 517评论 0 4