BTrace学习总结
一、简介:
在生产环境中经常遇到格式各样的问题,如OOM或者莫名其妙的进程死掉。一般情况下是通过修改程序,添加打印日志;然后重新发布程序来完成。然而,这不仅麻烦,而且带来很多不可控的因素。有没有一种方式,在不修改原有运行程序的情况下获取运行时的数据信息呢?如方法参数、返回值、全局变量、堆栈信息等。Btrace就是这样一个工具,它可以在不修改原有代码的情况下动态地追踪java运行程序,通过hotswap技术,动态将跟踪字节码注入到运行类中,对运行代码侵入较小,对性能上的影响可以忽略不计。
在下列情况时可以使用BTrace进行分析:
1、接口性能变慢,分析每个方法的耗时情况;
2、当在Map中插入大量数据,分析其扩容情况;
3、分析哪个方法调用了System.gc(),调用栈如何;
4、执行某个方法抛出异常时,分析运行时参数;
5、..................
二、安装:
1、安装JDK;
2、下载BTrace的压缩包,这里使用的是BTrace 1.3.11版本,可以到下面地址下载:
http://www.voidcn.com/link?url=https://github.com/btraceio/btrace/releases/tag/v1.3.11
3、将BTrace包解压,在系统的环境变量上添加变量BTRACE_HOME,并设置其路径为BTrace的路径,同时在PATH变量中添加上路径%BTRACE_HOME%\bin;
4、编辑%BTRACE_HOME%\bin\btrace.bat文件,将其中的-Dcom.sun.btrace.unsafe=false改为-Dcom.sun.btrace.unsafe=true;
5、btrace命令的语法说明:
btrace [-I <include-path>] [-p <port>] [-cp <classpath>] <pid> <btrace-script> [<args>]
1)没有这个表明跳过预编译;
2)include-path:指定用来编译脚本的头文件路径(关于预编译可参考例子ThreadBean.java);
3)port:btrace agent端口,默认是2020;
4)classpath:编译所需类路径,一般是指btrace-client.jar等类所在路径;
5)pid:java进程id;
6)btrace-script:btrace脚本可以是.java文件,也可以是.class文件;
7)args:传递给btrace脚本的参数, 在脚本中可以通过$(), $length()来获取这些参数(定义在BTraceUtils中);
三、Demo:
(一)JavaSE应用Demo:
1、编写测试功能实现类:
public class Calculator { public int add(int a, int b) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return a + b; } } |
2、编写调用代码:
public class App { public static void main( String[] args ) { Calculator calc = new Calculator(); Random random = new Random(); while (true) { int a = random.nextInt(10); int b = random.nextInt(20); int c = calc.add(a, b); System.out.println(String.format("%d + %d = %d", a, b, c)); } } } |
上面的代码无限循环调用Calculator .add方法并输出调用结果;
3、运行程序,可以看到屏幕上不停的输出各种加法运算的表达式;
4、编写btrace脚本:
@BTrace(unsafe = true) public class BTraceTest { @OnMethod(clazz = "com.ucar.test.Calculator", method = "add", location = @Location(Kind.RETURN)) public static void traceTest(int a, int b, @Return int sum) { println(String.format("%d + %d = %d", a, b, sum)); } } |
@BTrace注解中要加上unsafe=true,否则运行btrace脚本时会因为安全机制导致报错而无法执行脚本;
@OnMethod注解中的clazz表示要跟踪的类名,method表示要跟踪的方法名称,location表示在什么时候进行拦截;
5、运行btrace脚本,可以看到前面输出的加法运算表达式也能在这个窗口上输出;
运行btrace脚本的命令为:
btrace 3856 BTraceTest.java
其中3856为刚才运行的java程序的进程ID;
(二)web应用Demo:
1、新建SpringMVC的web应用程序(参考https://www.cnblogs.com/laoxia/p/9311442.html);
2、实现Controller:
@RestController @RequestMapping("/btrace") public class BTraceController { @RequestMapping("/arg1") public String arg1(@RequestParam("name") String name) { return "hello: " + name; } } |
3、生成war包并放到tomcat的webapp目录下,启动tomcat,浏览器中打开URL地址:http://localhost:8080/test/btrace/arg1?name=aaaaa,页面上应该能正常打印出“hello: aaaaa”;
4、编写BTrace脚本:
@BTrace(unsafe = true) public class PrintArgSimple { @OnMethod(clazz = "com.ucar.test.controller.BTraceController", method = "arg1", location = @Location(Kind.RETURN)) public static void anyRead(@ProbeClassName String pcn, // 被拦截的类名 @ProbeMethodName String pmn, //被拦截的方法名 AnyType[] args //被拦截的方法的参数值) { BTraceUtils.printArray(args); BTraceUtils.println("className: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println(); } } |
注意:需要在maven的POM文件中引入btrace-client.jar, btrace-boot.jar和btrace_agent.jar三个文件或者直接引入这三个jar包;
5、运行脚本,然后在浏览器中请求第三步的URL地址,这时候就能看到屏幕上打印出运行过程中的相关信息;
运行BTrace的命令为:
btrace 1256 PrintArgSimple.java
其中1256为这个web应用进程的进程ID;
6、注意:需要将web应用打包成war放到tomcat下运行,如果直接在idea下运行会报错;
四、拦截时机:
1、Kind.ENTRY:入口拦截,默认值;
2、Kind.RETURN:拦截返回值,只有把拦截位置定义为Kind.RETURN,才能获取方法的返回结果@Return和执行时间@Duration;
3、Kind.THROW:发生异常时拦截;
4、Kind.LINE:拦截某一行,可以监控代码是否执行到指定的位置;
5、Kind.CALL:分析方法中调用其它方法的执行情况,比如在execute方法中,想获取add方法的执行耗时,必须把where设置成Where.AFTER;
五、技巧:
1、拦截构造函数:
指定method = "<init>"即可拦截指定类的构造函数;
2、拦截同名函数:拦截同名重载方法,只需要在BTrace脚本的方法中声明与之对应的参数即可。
比如有如下两个同名方法:
@RequestMapping("/same1") public String same(@RequestParam("name") String name) { return "hello: " + name; } @RequestMapping("/same2") public User same(@RequestParam("id") int id, @RequestParam("name") String name) { return new User(id, name); } |
编写如下的btrace脚本即可拦截:
@OnMethod(clazz = "org.zero01.monitor_tuning.controller.BTraceController", method = "same") public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name) { BTraceUtils.println("ClassName: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println("name: " + name); BTraceUtils.println(); } @OnMethod(clazz = "org.zero01.monitor_tuning.controller.BTraceController", method = "same") public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int id, String name) { BTraceUtils.println("ClassName: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println("id: " + id); BTraceUtils.println("name: " + name); BTraceUtils.println(); } |
3、拦截返回值:
指定location=@Location(Kind.RETURN),并且在方法的参数里面加上@Return AnyType result即可接收返回值;
4、拦截异常:
@BTrace public class PrintOnThrow { @TLS static Throwable currentException; @OnMethod( clazz="java.lang.Throwable", method="<init>" ) public static void onthrow(@Self Throwable self) { // @Self其实就是拦截了this currentException = self; } @OnMethod( clazz="java.lang.Throwable", method="<init>" ) public static void onthrow1(@Self Throwable self, String s) { currentException = self; } @OnMethod( clazz="java.lang.Throwable", method="<init>" ) public static void onthrow1(@Self Throwable self, String s, Throwable cause) { currentException = self; } @OnMethod( clazz="java.lang.Throwable", method="<init>" ) public static void onthrow2(@Self Throwable self, Throwable cause) { currentException = self; } @OnMethod( clazz="java.lang.Throwable", method="<init>", location=@Location(Kind.RETURN) ) public static void onthrowreturn() { if (currentException != null) { // 打印异常堆栈 BTraceUtils.Threads.jstack(currentException); BTraceUtils.println("====================="); // 打印完之后就置空 currentException = null; } } } |
在命令行里运行该脚本,访问相应的接口后,即可输出异常堆栈;即使异常被try catch给隐藏起来了,这个脚本也一样能揪出来。
5、拦截指定行:
@BTrace public class PrintLine { @OnMethod( clazz="org.zero01.monitor_tuning.controller.BTraceController", method="exception", location=@Location(value=Kind.LINE, line=43) // 拦截第43行 ) public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) { BTraceUtils.println("ClassName: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println("line: " + line); BTraceUtils.println(); } } |
如果没有任何输出的话,就代表那一行没有被执行到,所以没被拦截。这种拦截某一行的方式,不适用于判断是否有异常,只能单纯用于判断某一行是否被执行了。
6、拦截复杂参数:
比如要拦截下面方法的复杂参数类型User:
@RequestMapping("/arg2") public User arg2(User user) { return user; } |
可以使用下面的btrace脚本拦截:
@BTrace public class PrintArgComplex { @OnMethod( clazz = "org.zero01.monitor_tuning.controller.BTraceController", method = "arg2", location = @Location(Kind.ENTRY) ) public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user) { //print all fields BTraceUtils.print("print all fields: "); BTraceUtils.printFields(user); //print one field Field oneFiled = BTraceUtils.field("org.zero01.monitor_tuning.vo.User", "name"); BTraceUtils.println("print one field: " + BTraceUtils.get(oneFiled, user)); BTraceUtils.println("ClassName: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println(); } } |
7、拦截环境变量:
@BTrace public class PrintJinfo { static { // 打印系统属性 BTraceUtils.println("System Properties:"); BTraceUtils.printProperties(); // 打印JVM参数 BTraceUtils.println("VM Flags:"); BTraceUtils.printVmArguments(); // 打印环境变量 BTraceUtils.println("OS Enviroment:"); BTraceUtils.printEnv(); // 退出脚本 BTraceUtils.exit(0); } } |
8、使用正则表达式拦截:
@BTrace public class PrintRegex { @OnMethod( // 类名也可以使用正则表达式进行匹配 clazz = "org.zero01.monitor_tuning.controller.BTraceController", // 正则表达式需要写在两个斜杠内 method = "/.*/" ) public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn) { BTraceUtils.println("ClassName: " + pcn); BTraceUtils.println("MethodName: " + pmn); BTraceUtils.println(); } } |
六、注意事项:
1、@ProbeClassName String clazz:此处String不能写为java.lang.String;
2、@OnMethod(clazz="com.alibaba.security.acl.support.PermissionFactory", method="createPermission", type="com.alibaba.security.acl.support.AbstractPermission(java.lang.String,java.lang.String,com.alibaba.security.acl.support.PermissionDefiner)")
此处得String必须写成java.lang.String;
3、BTrace脚本默认只能本地运行,也就是只能调试本地的Java进程。如果需要在本地调试远程的Java进程的话,是需要自己去修改BTrace源码的;
4、BTrace脚本在生产环境下可以使用,但是被修改的字节码不会被还原。所以我们需要先在本地调试好BTrace脚本,然后才能放到生产环境下使用。并且需要注意BTrace脚本中不能含有影响性能或消耗资源较多的代码,不然会导致线上的服务性能降低。
七、其他:
1、其他命令行工具说明:
(1) Btracec:用于预编译BTrace脚本,用于在编译时期验证脚本正确性。
btracec [-I <include-path>] [-cp <classpath>] [-d <directory>] <one-or-more-BTrace-.java-files>
参数意义同btrace命令一致,directory表示编译结果输出目录。
(2) Btracer:btracer命令同时启动应用程序和BTrace脚本,即在应用程序启动过程中使用BTrace脚本。而btrace命令针对已运行程序执行BTrace脚本。
btracer <pre-compiled-btrace.class> <application-main-class> <application-args>
参数说明:
pre-compiled-btrace.class表示经过btracec编译后的BTrace脚本。
application-main-class表示应用程序代码;
application-args表示应用程序参数。
2、方法上的注解:
(1) @ OnMethod用来指定trace的目标类和方法以及具体位置,被注解的方法在匹配的方法执行到指定的位置会被调用。
- "clazz"属性用来指定目标类名,可以指定全限定类名,比如"java.awt.Component",也可以是正则表达式(表达式必须写在"//"中,比如"/java\\.awt\\..+/")。
- "method"属性用来指定被trace的方法.表达式可以参考自带的例子(NewComponent.java和Classload.java,关于方法的注解可以参考MultiClass.java)。
- 有时候被trace的类和方法可能也使用了注解.用法参考自带例子WebServiceTracker.java。
- 针对注解也是可以使用正则表达式,比如像这个"@/com\\.acme\\..+/ ",也可以通过指定超类来匹配多个类,比如"+java.lang.Runnable"可以匹配所有实现了java.lang.Runnable接口的类.具体参考自带例子SubtypeTracer.java。
(2) @OnTimer定时触发Trace,时间可以指定,单位为毫秒,具体参考自带例子Histogram.java。
(3) @OnError当trace代码抛异常或者错误时,该注解的方法会被执行.如果同一个trace脚本中其他方法抛异常,该注解方法也会被执行。
(4) @OnExit当trace方法调用内置exit(int)方法(用来结束整个trace程序)时,该注解的方法会被执行.参考自带例子ProbeExit.java。
(5) @OnEvent用来截获"外部"btrace client触发的事件,比如按Ctrl-C中断btrace执行时,并且选择2,或者输入事件名称,将执行使用了该注解的方法,该注解的value值为具体事件名称。具体参考例子HistoOnEvent.java;
(6) @OnLowMemory当内存超过某个设定值将触发该注解的方法,具体参考MemAlerter.java;
(7) @OnProbe使用外部文件XML来定义trace方法以及具体的位置,具体参考示例SocketTracker1.java和java.net.socket.xml。
3、参数上的注解:
- @Self用来指定被trace方法的this,可参考例子AWTEventTracer.java和AllCalls1.java
- @Return用来指定被trace方法的返回值,可参考例子Classload.java
- @ProbeClassName (since 1.1)用来指定被trace的类名,可参考例子AllMethods.java
- @ProbeMethodName (since 1.1)用来指定被trace的方法名,可参考例子WebServiceTracker.java。
- @TargetInstance (since 1.1)用来指定被trace方法内部被调用到的实例,可参考例子AllCalls2.java
- @TargetMethodOrField (since 1.1)用来指定被trace方法内部被调用的方法名,可参考例子AllCalls1.java和AllCalls2.java。
4、属性上的注解:
- @Export该注解的静态属性主要用来与jvmstat计数器做关联, 使用该注解之后,btrace程序就可以向jvmstat客户端(可以用来统计jvm堆中的内存使用量)暴露trace程序的执行次数, 具体可参考例子ThreadCounter.java。
- @Property使用了该注解的trace脚本将作为MBean的一个属性,一旦使用该注解, trace脚本就会创建一个MBean并向MBean服务器注册, 这样JMX客户端比如VisualVM, jconsole就可以看到这些BTrace MBean, 如果这些被注解的属性与被trace程序的属性关联, 那么就可以通过VisualVM和jconsole来查看这些属性了, 具体可参考例子ThreadCounterBean.java和HistogramBean.java。
- @TLS用来将一个脚本变量与一个ThreadLocal变量关联, 因为ThreadLocal变量是跟线程相关的, 一般用来检查在同一个线程调用中是否执行到了被trace的方法, 具体可参考例子OnThrow.java和WebServiceTracker.java。
5、类上的注解:
- @com.sun.btrace.annotations.DTrace用来指定btrace脚本与内置在其脚本中的D语言脚本关联, 具体参考例子DTraceInline.java。
- @com.sun.btrace.annotations.DTraceRef用来指定btrace脚本与另一个D语言脚本文件关联, 具体参考例子DTraceRefDemo.java。
- @com.sun.btrace.annotations.BTrace用来指定该java类为一个btrace脚本文件。
6、BTrace文件下的samples文件夹下包含了很多的示例,这些示例说明如下:
AWTEventTracer.java -演示了对EventQueue.dispatchEvent()事件进行trace的做法,可以通过instanceof来对事件进行过滤,比如这里只针对focus事件trace. AllLines.java -演示了如何在被trace的程序到达probe指定的类和指定的行号时执行指定的操作(例子中指定的行号是-1表示任意行). AllSync.java -演示了如何在进入/退出同步块进行trace. ArgArray.java -演示了打印java.io包下所有类的readXXX方法的输入参数. Classload.java -演示打印成功加载指定类以及堆栈信息. CommandArg.java -演示如何获取btrace命令行参数. Deadlock.java -演示了@OnTimer注解和内置deadlock()方法的用法 DTraceInline.java -演示@DTrace注解的用法 DTraceDemoRef.java -演示@DTraceRef注解的用法. FileTracker.java -演示了如何对File{Input/Output}Stream构造函数中初始化打开文件的读写文件操作进行trace. FinalizeTracker.java -演示了如何打印一个类所有的属性,这个在调试和故障分析中非常有用.这里的例子是打印FileInputStream类的close() /finalize()方法被调用时的信息. Histogram.java -演示了统计javax.swing.JComponent在一个应用中被创建了多少次. HistogramBean.java -同上例,只不过演示了如何与JMX集成,这里的map属性通过使用@Property注解被暴露成一个MBean. HistoOnEvent.java -同上例,只不过演示了如何在通过按ctrl+c中断当前脚本时打印出创建次数,而不是定时打印. JdbcQueries.java -演示了聚合(aggregation)功能.关于聚合功能可参考DTrace. JInfo.java -演示了内置方法printVmArguments(), printProperties()和printEnv()的用法 JMap.java -演示了内置方法dumpHeap()的用法.即将目标应用的堆信息以二进制的形式dump出来 JStack.java -演示了内置方法jstackAll()的用法,即打印所有线程的堆栈信息. LogTracer.java -演示了如何深入实例方法(Logger.log)并调用内置方法(field() )打印私有属性内容. MemAlerter.java -演示了使用@OnLowMememory注解监控内存使用情况.即堆内存中的年老代达到指定值时打印出内存信息. Memory.java -演示每隔4s打印一次内存统计信息. MultiClass.java -演示了通过使用正则表达式对多个类的多个方法进行trace. NewComponent.java -使用计数器每隔一段时间检查当前应用中创建java.awt.Component的个数. OnThrow.java -当抛出异常时,打印出异常堆栈信息. ProbeExit.java -演示@OnExit注解和内置exit(int)方法的用法 Profiling.java -演示了对profile的支持. //我执行没成功, BTrace内部有异常 Sizeof.java -演示了内置的sizeof方法的使用. SocketTracker.java -演示了对socket的creation/bind方法的trace. SocketTracker1.java -同上,只不过使用了@OnProbe. SysProp.java -演示了使用内置方法获取系统属性,这里是对java.lang.System的getProperty方法进行trace. SubtypeTracer.java -演示了如何对指定超类的所有子类的指定方法进行trace. ThreadCounter.java -演示了在脚本中如何使用jvmstat计数器. (jstat -J-Djstat.showUnsupported=true -name btrace.com.sun.btrace.samples.ThreadCounter.count需要这样来从外部通过jstat来访问) ThreadCounterBean.java -同上,只不过使用了JMX. ThreadBean.java -演示了对预编译器的使用(并结合了JMX). ThreadStart.java -演示了脚本中DTrace的用法. Timers.java -演示了在一个脚本中同时使用多个@OnTimer URLTracker.java -演示了在每次URL.openConnection成功返回时打印出url.这里也使用了D语言脚本. WebServiceTracker.java -演示了如何根据注解进行trace. |
7、参考文档:
http://huanghaifeng1990.iteye.com/blog/2121419
http://agapple.iteye.com/blog/962119
http://agapple.iteye.com/blog/1005918
BTrace学习总结的更多相关文章
- 【JVM 知识体系框架总结】
JVM 内存分布 线程共享数据区: 方法区->类信息,静态变量 堆->数组对象 线程隔离区 虚拟机栈-> 方法 本地方法栈->本地方法库 native 堆.程序计数器 JVM ...
- [转帖]【JVM 知识体系框架总结】
[JVM 知识体系框架总结] https://www.cnblogs.com/mousycoder/p/11612448.html JVM 内存分布 线程共享数据区:方法区->类信息,静态变量堆 ...
- java 学习之路
一.基础篇 1.1 JVM 1.1.1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http://www.jcp.org/en/jsr/detail?id=133 http://i ...
- Java BTrace实战(1)--BTrace的入门和使用
前言: 对线上的java服务, 往往采用日志进行问题处理和分析. 倘若日志缺乏相关的信息时, 那又该如何处理? 远程调试会影响服务的正常工作, 修改代码重新部署的方案其实时性和灵活性难以保证(线上服务 ...
- BTrace使用小结
简介 BTrace是一个安全的JVM动态追踪工具,最初为原Sun公司Kenai项目下面的一个子项目. 典型的使用场景是,“我要查个问题,可那个方法没有打印入口参数和返回结果日志”,“我想看某个方法的执 ...
- Java学习路线(转)
原文:http://www.hollischuang.com/archives/489 一.基础篇 1.1 JVM 1.1.1. Java内存模型,Java内存管理,Java堆和栈,垃圾回收 http ...
- Java学习---面试基础知识点总结
Java中sleep和wait的区别 ① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类. sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线 ...
- 如何学习Java?
一点感悟 java作为一门编程语言,在各类编程语言中作为弄潮儿始终排在前三的位置,这充分肯定了java语言的魅力,在实际项目应用中,我们已经无法脱离javaa(Ps当然你可以选择不使用),但它的高性能 ...
- 阿里P7/P8学习路线图——技术封神之路
一.基础篇 JVM JVM内存结构 堆.栈.方法区.直接内存.堆和栈区别 Java内存模型 内存可见性.重排序.顺序一致性.volatile.锁.final 垃圾回收 内存分配策略.垃圾收集器(G1) ...
随机推荐
- jvm内存配置参数
请看下面题目: 对于jvm内存配置参数: -Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3 其最小内存值和Survior区总大小分别是: a. 51 ...
- WIN10-缩放与布局
HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\AppliedDPI230%----- 221225%----- 218220%----- ...
- python模块大全
python模块大全2018年01月25日 13:38:55 mcj1314bb 阅读数:3049 pymatgen multidict yarl regex gvar tifffile jupyte ...
- CRM工具简介
pacemaker是高可用集群中的CRM(Cluster Resource Manager)资源管理层,他是一个服务,可以作为一个单独的服务启动,不过在如果使用corosync1.4中,我们可以设置c ...
- SQL注入之Sqli-labs系列第十八关(基于错误的用户代理,头部POST注入)
开始挑战第十八关(Header Injection - Uagent field - Error based) 常见的HTTP注入点产生位置为[Referer].[X-Forwarded-For].[ ...
- react-navigation实现页面框架(转载)
初始化一个RN项目 react-native init page_framework page.json { "name": "page_framework", ...
- matlab一行太长
太长了,一行写不下,所以用...接下一行 例子: x=[ mvnrnd( mu_real(:,1) , cov_real(:,:,1) , round(N*a_real(1)) )' ,... mvn ...
- Unity 3D还原Scene场景、市面多数游戏视角高度自定义、第三人称视角分离功能:平移、拖动、看向中心等
Unity视角的高度自定义 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享. ...
- java学习之动手实验
一, 1,JAVA的基本运行单位是类 2,类的成员:成员变量,构造方法,普通方法和内部类 3,成员变量种类:字符类型:char 布尔类型:boolean 数值类型:byte, ...
- java-this和super的区别
1.this和super都代表什么: - this:代表当前对象的引用,谁来调用我,我就代表谁 - super:代表当前对象父类的引用 - super(...)或者this(...)必须放在构造方法的 ...