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

java 调优之 Btrace_第1张图片
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

你可能感兴趣的:(java 调优之 Btrace)