千家信息网

Java运行时如何使用追踪工具BTrace

发表于:2024-12-12 作者:千家信息网编辑
千家信息网最后更新 2024年12月12日,这期内容当中小编将会给大家带来有关Java运行时如何使用追踪工具BTrace,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. BTrace是什么?Btrace是由
千家信息网最后更新 2024年12月12日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  -o              The 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.javaClassload.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.javaAllCalls1.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.javaAllCall2.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.CHECKCASTcheckcast
Kind.ENTRY方法进入。意指进入匹配probe点,跟你@Location设置的clazz和method没有任何关系
Kind.ERROR错误,异常没有捕获,返回
Kind.FIELD_GETfield获取
Kind.FIELD_SETfield设置
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.javaHistogramBean.java来了解用法

  • @com.sun.btrace.annotations.TLS BTrace字段使用该注解来说明它自己是一个线程本地字段(thread local field).注意你只能在@OnMethod注解后的函数里访问这样的字段。每个Java线程都有一个这个字段的拷贝。为了让这样的方式能够工作,这个字段的类型只能是immutable(比如原始类型) 或者是cloneable (实现了Cloneable接口并且覆盖了clone()函数)的。这些线程本地字段可以被BTrace程序用来识别它是否在同一个线程里执行了多个探测操作。参考例子OnThrow.javaWebServiceTracker.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. 注意事项

  1. 脚本中方法参数需要跟原方法参数类型保持一致

  2. 脚本中不允许使用除btrace之外的类,拼接字符串使用BTraceUtils.strcat(),打印使用BTraceUtils.println(),获取线程使用BTraceUtils.Threads

  3. BTrace植入过的代码,会一直在,直到应用重启为止。所以即使Btrace退出了,业务函数每次执行时都会执行Btrace植入的代码

上述就是小编为大家分享的Java运行时如何使用追踪工具BTrace了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

0