Java运行时如何使用追踪工具BTrace
这期内容当中小编将会给大家带来有关Java运行时如何使用追踪工具BTrace,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
1. BTrace是什么?
Btrace是由sundararajan在2009年6月开发的一个开源项目,是一种动态跟踪分析一个运行中的Java应用程序的工具。 BTrace是一个为Java平台开发的安全、动态的追踪工具。BTrace动态地向目标应用程序的字节码注入追踪代码(字节码追踪),这些追踪字节码追踪代码使用Java语言表达,也就是BTrace的脚本。
2. BTrace约束
为了保证追踪动作是"只读"的(也就是这些动作不可以修改被追踪程序的状态)和有限度的(比如在固定时间里结束)。一个BTrace程序只允许完成一些指定的动作。下面是BTrace一些不可以完成的事情:
不能创建新的对象
不能创建新的数组
不能抛出异常
不能捕获异常
不能进行任何的实例函数或者静态函数 - 只有com.sun.btrace.BTraceUtils类中的静态函数或者BTrace程序自己声明的函数才可以被BTrace调用
不可以在目标程序的类,或者对象的静态或者实例级别的field进行赋值。但是,BTrace自身的类是可以给它的静态field进行赋值的
不能有outer,inner,嵌套的或者本地类。
不能有同步代码块或者同步的函数
不能有循环语句(for,while, do..while)
不能继承其它类(父类只能是java.lang.Object)
不能实现接口
不能包含断言(assert)语句
不能使用类字面值
这上面的种种限制可以通过一个配置改变:unsafe=true,在使用BTrace注解时修改该属性的默认值(false)为true,即@BTrace(unsafe=true);也可以启动选项中显式声明-Dcom.sun.btrace.unsafe=true(响应也有-u参数);现在你可以为所欲为了。BUT,这样做之前最好考虑好风险并再三检查脚本,请斟酌使用!
3. BTrace安装
btrace git下载地址 ,下载release版本下来直接解压就可以使用。
3.1 基本语法
btrace脚本
btrace命令行工具运行命令如下:
btrace常用选项:[-I ] [-p ] [-cp ]
参数说明:
where possible options include: --version Show the version -v Run in verbose mode -oThe path to store the probe output (will disable showing the output in console) -u Run in trusted mode -d Dump the instrumented classes to the specified path -pd The search path for the probe XML descriptors -classpath Specify where to find user class files and annotation processors -cp Specify where to find user class files and annotation processors -I Specify where to find include files -p Specify port to which the btrace agent listens for clients -statsd Specify the statsd server, if any
include-path : 是一些用来查找头文件的目录。BTrace包含一个简单的预处理,支持# define,# + include和条件编译。它不像一个完整的C / c++预处理器-而是一个有用的子集。详见demo代码"ThreadBean.java",如果没有显式的声明选项-I,Btrace跳过预处理程序调用步骤。
port: BTrace代理程序所侦听的端口,这是可选的选项。默认是2020
classpath: 是一些用来查找jar文件的目录。默认是当前目录"."
pid:是要追踪目标程序id
btrace-script: 就是追踪程序本身。如果这是个java文件,那么提交前会进行编译。否则,它被认为已预编译(即它必须是一个类)并提交
arguments: 这是传递给BTrace程序的参数。BTrace程序可以通过内置的符号来引用这些参数,length是这些参数的个数。
在samples目录下有很多示例,并且有的跟踪很有用可直接使用.
4. BTrace的注解
4.1 方法注解
@com.sun.btrace.annotations.OnMethod 该注解可用来指定目标类,目标方法,以及目标方法里的"位置"。加了该注解后的操作方法会在对应的方法运行到指定的"位置"时被执行。该注解中,目标类用"clazz"属性来指定,而目标方法用"method"属性来指定。"clazz"可以是类的全路径(比如java.awt.Component或者用两个反斜杠中间的正则表达式,参考例子NewComponent.java和Classload.java来看它们的用法,正则表达式可以匹配0个或多个目标类,这个时候多个类都会被进行动态指令更换。如/java.awt.+/匹配java.awt包下的所有类)。方法名也可以用这样的正则表达式 来匹配零个或者多个多个方法。参考例子MultiClass.java来查看用法。还有一种方法来指定追踪类和函数。被追踪的类和函数可以用注解来指定。比如,如果"clazz"属性是@javax.jws.Webservice.那么BTrace会会把所有注解是这个的函数都进行动态指令更换。类似地,方法级别的注解也可以用来执行方法。参看例子WebServiceTracker.java来了解如何使用。可以把正则表达式和注解放在一起用,比如@/com.acme..+/可以匹配任何类,只要这个类的注解能跟那段正则表达式匹配即可。可以通过指定父类来匹配多个类名,比如+java.lang.Runnable就可以匹配所有实现了java.lang.Runnable这个接口的类。参考例子SubtypeTracer.java来看它的用法。
@com.sun.btrace.annotations.OnTimer 该注解可以用来执行那些需要周期性(间隔是毫秒)的追踪操作。参考Histogram.java来看它的用法。
@com.sun.btrace.annotations.OnError 该注解可以用来指定当任何异常抛出时需要执行的操作。被该注解修饰后的BTrace函数会在同一个BTrace类的其他操作方法抛出异常时执行。
@com.sun.btrace.annotations.OnExit 该注解用来执行党BTrace代码调用了exit(int)结束追踪会话后需要执行的操作。参考例子ProbeExit.java来了解如何使用。
@com.sun.btrace.annotations.OnEvent 该注解用来追踪函数与"外部"的事件关联起来。当BTrace客户端发送了一个"事件"后,该注解里的操作就会被执行。客户端发送的事件可能是由用户触发的(比如按下Ctrl-C)。事件的名字是个字符串,这样追踪操作就只会在对应的事件触发后被执行。到目标为止,BTrace命令行客户端会在用户按下Ctrl-C后发送事件,参考例子HistoOnEvent.java来了解用法。
@com.sun.btrace.annotations.OnLowMemory 该注解可以用来追踪特定内存阈值被用光的事件。参看例子MemAlerter.java了解用法。
@com.sun.btrace.annotations.OnProbe 该注解可以用来避免使用BTrace脚本的内部类。@OnProbe探测点被映射到一个或多个@OnMethod上。目前这个映射是通过一个XML探测描述文件类指定的(这个文件会被BTrace代理所使用)。参考例子SocketTracker1.java和对应的描述文件java.net.socket.xml.当运行这个例子时,xml文件需要放在目标JVM所有运行的目录下(或者修改btracer.bat中的probeDescPath选项来指向任意的xml文件)。
@com.sun.btrace.annotations.Location:该注解在一个traced/probed方法中指定一个特定的"位置"
@com.sun.btrace.annotations.Simpled:标记@OnMethod注解处理器采样。采样处理程序时并不是所有的事件将被追踪,只有一个统计样品与给定的意思。在默认情况下使用一种自适应采样。BTrace将增加或减少样品之间的调用数量保持平均时间窗口,因此减少整体的开销。
4.2 参数相关的注解
@com.sun.btrace.annotations.Self:该注解把一个参数标识为保留了目标函数所指向的this的值。参考例子AWTEventTracer.java和AllCalls1.java.
@com.sun.btrace.annotations.Return:该注解说明这个参数保存目标函数的返回值。参考例子Classload.java
@com.sun.btrace.annotations.ProbeClassName:所修饰的参数保留了探测类的类名 。参看AllMethods.java(有多个探测类)
@com.sun.btrace.annotations.ProbeMethodName:所修饰的参数保留了探测函数的函数名。参考WebServiceTracker.java(多个探测函数)
@com.sun.btrace.annotations.TargetInstance:修饰的参数保留了被调用的实例。参考例子AllCall2.java.
@com.sun.btrace.annotations.TargetMethodOrField:该注解修饰的参数保存了调用的函数名。参考AllCalls1.java 和AllCall2.java
@com.sun.btrace.annotations.Duration:探测方法参数标记为持续时间值的接收者,即目标方法执行的时间,单位纳秒。只是用带Location属性的@OnMethod,并且需要配合Kind.ERROR或者Kind.RETURN使用
4.3 无注解的参数
没有注解的BTrace探测函数参数是用来作签名匹配的,因为他们必须必须在固定的位置上出现。然而,它们可以和其他的注解的参数进行交换。如果一个参数的类型是_AnyType[]_,它就会"吃"掉所所有剩下的参数。没有注解的参数的具体含义与他们所在的位置有关:
名称 | 作用 |
---|---|
Kind.ARRAY_GET | 数组元素加载 |
Kind.ARRAY_SET | 数组元素存储 |
Kind.CALL | 方法调用 |
Kind.CATCH | 异常捕获 |
Kind.CHECKCAST | checkcast |
Kind.ENTRY | 方法进入。意指进入匹配probe点,跟你@Location设置的clazz和method没有任何关系 |
Kind.ERROR | 错误,异常没有捕获,返回 |
Kind.FIELD_GET | field获取 |
Kind.FIELD_SET | field设置 |
Kind.INSTANCEOF | 实例检测 |
Kind.LINE | 源代码行号 |
Kind.NEW | 创建新实例 |
Kind.NEWARRAY | 新的数组对象被创建 |
Kind.RETURN | 意指从某个匹配probe的方法中调用了匹配A class method的点,一定要和claz |
Kind.SYNC_ENTRY | 进入一个同步方法锁 |
Kind.SYNC_EXIT | 离开一个同步方法锁 |
Kind.THROW | 抛出异常 |
4.4 字段相关的注解
@com.sun.btrace.annotations.Export BTrace字段使用该注解来说明它已经被映射到一个jvmstat计数器上。使用该注解,BTrace程序可以把追踪计数器暴露给外部的jvmstat客户端(比如jstat)。参考例子ThreadCounter.java
@com.sun.btrace.annotations.Property 该注解可以把一个字段标识为一个MBean属性。如果一个BTrace类至少有一个静态的字段使用了该注解。那么一个MBean就会被创建并且注册到平台MBean服务器上。JMX客户端比如VisualVM,jconsole可以访问这个字段来查看BTrace的MBean。在把BTrace附加到目标程序上后,你可以把VisualVM或者jconsole也附加到同一个目标程序上来查看刚创建好的MBean属性。通过VisualVM或者jconsole,你可以通过MBeans tab页来查看BTrace相关的域,然后查看它们的值。参考例子ThreadCounterBean.java 和HistogramBean.java来了解用法
@com.sun.btrace.annotations.TLS BTrace字段使用该注解来说明它自己是一个线程本地字段(thread local field).注意你只能在@OnMethod注解后的函数里访问这样的字段。每个Java线程都有一个这个字段的拷贝。为了让这样的方式能够工作,这个字段的类型只能是immutable(比如原始类型) 或者是cloneable (实现了Cloneable接口并且覆盖了clone()函数)的。这些线程本地字段可以被BTrace程序用来识别它是否在同一个线程里执行了多个探测操作。参考例子OnThrow.java和WebServiceTracker.java
4.5 类相关的注解
@com.sun.btrace.annotations.DTrace该注解用来把一小段D脚本(嵌在BTrace 的java类中)和BTrace程序关联起来。参考例子DTraceInline.java
@com.sun.btrace.annotations.DTraceRef 和上个注解一样,不同的是D脚本是在独立的文件中,不是嵌在java类中。
@com.sun.btrace.annotations.BTrace必须使用该注解来指定一个Java类是BTrace程序。BTrace编译器会强制查找该注解,BTrace代理也会检查这个是否有该注解。如果没有,则提示错误,并且不会执行。
5. 使用示例
5.1 准备示例代码
package com.gitee.funcy.jtools.btrace;import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.net.URLConnection;/** * 该程序周期性地进行网络读取操作 * * @author chengyan * @date 2019-11-03 10:41 下午 */public class HoldNetTask implements Runnable{ public void visitWeb(String strUrl) { URL url = null; URLConnection urlconn = null; InputStream is = null; try { url = new URL(strUrl); urlconn = url.openConnection(); is = urlconn.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); StringBuffer bs = new StringBuffer(); String l = null; while((l = bufferedReader.readLine()) != null) { bs.append(l).append("\r\n"); } } catch (Exception e) { e.printStackTrace(); } finally { if(is != null) { try { is.close(); } catch (Exception e) { } } } } @Override public void run() { while(true) { visitWeb("http://www.sina.com.cn"); } } public static void main(String[] args) { new Thread(new HoldNetTask()).start(); }}
5.2 导入相关jar包
1. 直接导入jar包
BTrace对应的java包在Btrace release文件的build目录下,一共有三个jar包:
btrace-agent.jarbtrace-boot.jarbtrace-client.jar
2. 使用maven引入jar包
也可使用maven引入依赖包,在pom文件中添加以下内容:
...省略其他 1.3.11.3 ...省略其他 com.sun.tools.btrace btrace-agent ${btrace.version} com.sun.tools.btrace btrace-boot ${btrace.version} com.sun.tools.btrace btrace-client ${btrace.version} btrace-repo btrace-repo https://dl.bintray.com/btraceio/maven/ true false
需要注意的是,maven中央仓库中可能没有btrace的最新版本,为此需要配置额外的仓库。
5.3 监控函数的耗时
使用BTrace脚本可以通过正则表达式指定监控特定类的特定方法的耗时,以下代码将监控所有类中名为visitWeb()
的函数的执行时间。
package com.gitee.funcy.jtools.btrace;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.Kind;import com.sun.btrace.annotations.Location;import com.sun.btrace.annotations.OnMethod;import com.sun.btrace.annotations.ProbeClassName;import com.sun.btrace.annotations.ProbeMethodName;import com.sun.btrace.annotations.TLS;import static com.sun.btrace.BTraceUtils.*;/** * {这里添加描述} * * @author chengyan * @date 2019-11-03 12:13 上午 */@BTracepublic class PrintTimes { @TLS private static long startTime = 0; /** * 方法调用开始 * clazz = "/.+/" : 监控做任意类 * method="/visitWeb/":监控visitWeb方法 */ @OnMethod(clazz = "/.+/", method="/visitWeb/") public static void startMethod() { startTime = timeMillis(); } @OnMethod(clazz = "/.+/", method="/visitWeb/", location=@Location(Kind.RETURN)) public static void endMethod(@ProbeClassName String pcm, @ProbeMethodName String pmn) { println(pcm + "." + pmn + " [Time taken: " + str(timeMillis() - startTime) + "ms]"); }}
运行结果:
$ btrace 42188 PrintTimes.javacom.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 55ms]com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 50ms]com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 52ms]com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 49ms]com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 54ms]com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb [Time taken: 54ms]
5.4 监控函数参数
除了执行时间,BTrace也可输出函数的参数:
package com.gitee.funcy.jtools.btrace;import com.sun.btrace.AnyType;import com.sun.btrace.BTraceUtils;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.OnMethod;import com.sun.btrace.annotations.ProbeClassName;import com.sun.btrace.annotations.ProbeMethodName;/** * {这里添加描述} * * @author chengyan * @date 2019-11-03 10:43 下午 */@BTracepublic class PrintArgs { @OnMethod(clazz = "/.*HoldNetTask/",method = "/visitWeb/") public static void anyWriteFile(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) { BTraceUtils.print(pcn + "-" + pmn); BTraceUtils.printArray(args); }}
运行结果:
$ btrace 46877 PrintArgs.javacom.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]com.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]com.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]com.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]com.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]com.gitee.funcy.jtools.btrace.HoldNetTask-visitWeb[http://www.sina.com.cn, ]
5.5 取得做任意行代码信息
通过BTrace脚本@Location
注解,可以指定程序运行到某一行代码时,触发某一行为。下例显示了通过BTrace脚本获取HoldNetTask
类第27行代码的信息(当目标程序运行到第27行时,触发BTrace脚本)。
package com.gitee.funcy.jtools.btrace;import static com.sun.btrace.BTraceUtils.*;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.Location;import com.sun.btrace.annotations.OnMethod;import com.sun.btrace.annotations.Kind;import com.sun.btrace.annotations.ProbeClassName;import com.sun.btrace.annotations.ProbeMethodName;/** * {这里添加描述} * * @author chengyan * @date 2019-11-04 10:02 下午 */@BTracepublic class AllLines { @OnMethod(clazz = "/.*HoldNetTask/", location = @Location(value = Kind.LINE, line = 27)) public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) { println(pcn + "." + pmn + ":" + line); }}
运行结果:
$ btrace 46877 AllLines.javacom.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
由脚本输出可以看到,BTrace正确识别出HoldNetTask类的第27行代码,正处于visitWeb()
函数的运行区间中。若将 @Location
中line的值设置为-1,则BTrace脚本将在每一行触发。
5.6 定时触发
BTrace脚本支持定时触发。可以周期性地执行某一行为,获取系统信息。下例使用@OnTimer
标记制定两个周期性任务,分别为每秒运行一次和每3秒运行一次。
package com.gitee.funcy.jtools.btrace;import com.sun.btrace.BTraceUtils;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.OnTimer;import static com.sun.btrace.BTraceUtils.jstackAll;import static com.sun.btrace.BTraceUtils.println;/** * {这里添加描述} * * @author chengyan * @date 2019-11-04 10:11 下午 */@BTracepublic class Timers { @OnTimer(1000) public static void getUpTime() { println(BTraceUtils.Strings.strcat("1000 msec:", BTraceUtils.Strings.str(BTraceUtils.Sys.VM.vmUptime()))); } @OnTimer(3000) public static void getStack() { jstackAll(); }}
运行结果:
$ btrace 46969 Timers.java1000 msec:276541000 msec:28647Thread[Attach Listener,9,system]Thread[Signal Dispatcher,9,system]Thread[Thread-1,9,system] java.net.PlainSocketImpl.socketAccept(Native Method) java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) java.net.ServerSocket.implAccept(ServerSocket.java:545) java.net.ServerSocket.accept(ServerSocket.java:513) com.sun.btrace.agent.Main.startServer(Main.java:668) com.sun.btrace.agent.Main.access$000(Main.java:63) com.sun.btrace.agent.Main$2.run(Main.java:128) java.lang.Thread.run(Thread.java:748)
在getUpTime()
方法中,指定了一个每秒运行一次的任务,并打印虚拟机的启动时间。在getStack()
方法中,指定了一个每3秒运行一次的任务,每次都将导出系统的线程快照。
5.7 获取类的属性
在程序运行过程中,BTrace可以在指定的位置获得对象实例的字段信息。比如,在本实例中,可以在调用URL.openConnection()
时查看实际打开的URL地地址。
package com.gitee.funcy.jtools.btrace;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.Location;import com.sun.btrace.annotations.OnMethod;import com.sun.btrace.annotations.Kind;import com.sun.btrace.annotations.ProbeClassName;import com.sun.btrace.annotations.ProbeMethodName;import com.sun.btrace.annotations.Self;import static com.sun.btrace.BTraceUtils.*;/** * {这里添加描述} * * @author chengyan * @date 2019-11-04 10:20 下午 */@BTracepublic class PrintField { @OnMethod(clazz = "/.*URL/", method = "/.*openConnection/", location = @Location(value = Kind.ENTRY)) public static void visitWebEntry(@Self Object self, @ProbeClassName String pcn, @ProbeMethodName String pmn) { println(pcn + "." + pmn); println(self); // 只能取值static变量 println(get(field(classOf(self), "protocolPathProp"))); // 获得实例变量 println(get(field(classOf(self), "host"), self)); println("==============="); }}
运行结果:
$ btrace 46969 PrintField.javajava.net.URL.openConnectionhttp://www.sina.com.cnjava.protocol.handler.pkgswww.sina.com.cn===============
这里监控URL对象,在调用openConnection()
方法时,获得当前对象实例self,然后获取对应实例的host等属性。
6. 注意事项
脚本中方法参数需要跟原方法参数类型保持一致
脚本中不允许使用除btrace之外的类,拼接字符串使用
BTraceUtils.strcat()
,打印使用BTraceUtils.println()
,获取线程使用BTraceUtils.Threads
BTrace植入过的代码,会一直在,直到应用重启为止。所以即使Btrace退出了,业务函数每次执行时都会执行Btrace植入的代码
上述就是小编为大家分享的Java运行时如何使用追踪工具BTrace了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。