利用hotswap技术,动态将跟踪字节码注入到运行类中,对运行代码侵入较小,对性能上的影响可以忽略不计
1、追踪方法的调用,参数返回值,执行时间,响应时间
2、定时打印调用次数,监控对某方法的调用
3、打印系统/JVM等信息,堆和非堆内存实时信息
4、接口性能变慢,分析每个方法的耗时情况;
5、当在Map中插入大量数据,分析其扩容情况;
6、分析哪个方法调用了System.gc(),调用栈如何;
7、执行某个方法抛出异常时,分析运行时参数;等等
BTrace Gradle方式安装:
1. 预先安装有Jdk, Git, Gradle
2. 下载代码: https://github.com/btraceio/btrace
3. 构建,如下,完成后在<btrace>/build/distributions/下有对应的构建包,解压将bin配置到path即可使用
cd <btrace> ./gradlew build ./gradlew buildDistributions
BTrace入门尝试,首先编写一个类,持久运行,模拟线上不断运行的系统
import java.util.Random; /** * Created by itworker365 on 5/17/2017. */ public class Calculator { 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))); } } private int c = 1; public int add(int a, int b) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return a + b; } }
编写BTrace文件BTraceTest.java内容如下,jps查到运行的系统进程号,运行命令btrace 进程号 BTraceTest.java
import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; /** * Created by itworker365 on 5/17/2017. */ @BTrace public class BTraceTest { private static long count; @TLS private static long startTime=0; static{ println("---------------------------JVM properties:---------------------------"); printVmArguments(); println("---------------------------System properties:------------------------"); printProperties(); println("---------------------------OS properties:----------------------------"); printEnv(); } //对Calculator的add方法的参数和返回值进行追踪 @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); jstack(); } //对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))); print("["); print(strcat("Time taken: ",str(timeMillis()-startTime))); println("]"); } //对Calculator的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))); } //每隔6秒打印一次count @OnTimer(6000) public static void trace4() { println(strcat("trace4:count: ", str(count))); } //获取 Calculator类的c属性的值 @OnMethod(clazz = "Calculator", method = "add", location = @Location(Kind.RETURN)) public static void trace5(@Self Object calculator) { println(get(field("Calculator", "c"), calculator)); } //每隔4秒打印一次印堆和非堆内存信息 @OnTimer(4000) public static void traceMemory() { println("heap:"); println(heapUsage()); println("no-heap:"); println(nonHeapUsage()); } //每隔4秒检测是否有死锁产生,并打印产生死锁的相关类信息、对应的代码行、线程信息 @OnTimer(4000) public static void trace6() { deadlocks(); } }
结果节选:
ProbeClassName: Calculator ProbeMethodName: add TargetInstance: null TargetMethodOrField : sleep count: 36 trace4:count: 36 heap: init = 16777216(16384K) used = 6802480(6643K) committed = 16252928(15872K) max = 253427712(247488K) no-heap: init = 2555904(2496K) used = 11498280(11228K) committed = 11796480(11520K) max = -1(-1K) 1 duration(nanos): 0 duration(s): 0 [Time taken: 1495003046282] trace1:a=7,b=8,sum=15 Calculator.add(Calculator.java:20) Calculator.main(Calculator.java:10)
注解说明-copy
1)类注解 @com.sun.btrace.annotations.BTrace指定该java类为一个btrace脚本文件。 2)属性注解 @TLS标注的属性可以在追踪脚本的方法中通讯 3)方法注解 @OnMethod:指定该方法在什么情况下被执行
clazz属性指定要跟踪的类的全限定类名,也可以用正则表达式,“/类名的Pattern/”匹配,如/javax\\.swing\\..*/;
用”+类名”追踪所有子类,如+java.lang.Runnable;
用”@xxx”追踪用该注解注解过的类,如@javax.jws.WebService。
method属性指定要追踪的方法名称,也可以用正则表达式。
location属性用@Location来指定该方法在目标方法执行前(后、异常、某行、某个方法调用)被执行。 @OnTimer:定时执行该方法。 @OnExit:当脚本运行Sys.exit(code)时执行该方法。 @OnError:当脚本运行抛出异常时执行该方法。 @OnEvent:脚本运行时Ctrl+C可以发送事件。 @OnLowMemory:指定一个内存阀值,低于阀值值执行该方法。 @OnProbe:指定一个xml文件来描述在什么时候执行该方法。 4)方法参数注解 @Self:指目标对象本身。 @Retrun:指目标程序方法返回值(需要配合Kind.RETURN)。 @ProbeClassName:指目标类名。 @ProbeMethodName:指目标方法名。 @targetInstance:指@Location指定的clazz和method的目标(需要配合Kind.CALL)。 @targetMethodOrField:指@Location指定的clazz和method的目标的方法或字段(需要配合Kind.CALL)。 @Duration:指目标方法执行时间,单位是纳秒(需要需要配合Kind.RETURN或Kind.ERROR一起使用)。 AnyType:获取对应请求的参数,泛指任意类型。 9.追踪时机参数 Kind.Entry:开始进入目标方法时,默认值。 Kind.Return:目标方法返回时。 Kind.Error:异常没被捕获被抛出目标方法之外时。 Kind.Throw:异常抛出时。 Kind.Catch:异常被捕获时。 Kind.Call:被调用时。 Kind.Line:执行到某行时。 10.其它 1)追踪构造函数用法:@OnMethod(clazz="java.net.ServerSocket",method="<init>”)。 2)追踪静态内部类:在类与内部类之间加上"$"@OnMethod(clazz="com.vip.MyServer$MyInnerClass", method="hello”)。 3)追踪同名函数:如果有多个同名的函数,可以在拦截函数上定义不同的参数列表。 4)追踪结果输出可以使用>将结果输出到指定文件。