为什么要有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
2. 入门示例
格式: 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