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

发布时间:2021-09-27 17:50 来源:亿速云 阅读:0 作者:柒染 栏目: 开发技术

这期内容当中小编将会给大家带来有关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 <pid> <btrace-script>脚本

btrace命令行工具运行命令如下:

btrace <options> <pid> <btrace source or .class file> <btrace arguments>
常用选项:
[-I <include-path>] [-p <port>] [-cp <classpath>]

参数说明:

where possible options include:
  --version             Show the version
  -v                    Run in verbose mode
  -o <file>             The path to store the probe output (will disable showing the output in console)
  -u                    Run in trusted mode
  -d <path>             Dump the instrumented classes to the specified path
  -pd <path>            The search path for the probe XML descriptors
  -classpath <path>     Specify where to find user class files and annotation processors
  -cp <path>            Specify where to find user class files and annotation processors
  -I <path>             Specify where to find include files
  -p <port>             Specify port to which the btrace agent listens for clients
  -statsd <host[:port]> 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[]_,它就会“吃”掉所所有剩下的参数。没有注解的参数的具体含义与他们所在的位置有关:

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.jar
btrace-boot.jar
btrace-client.jar
2. 使用maven引入jar包

也可使用maven引入依赖包,在pom文件中添加以下内容:

<properties>
	...省略其他
	<btrace.version>1.3.11.3</btrace.version>
</properties>

<dependencies>
	...省略其他
	<dependency>
		<groupId>com.sun.tools.btrace</groupId>
		<artifactId>btrace-agent</artifactId>
		<version>${btrace.version}</version>
	</dependency>
	<dependency>
		<groupId>com.sun.tools.btrace</groupId>
		<artifactId>btrace-boot</artifactId>
		<version>${btrace.version}</version>
	</dependency>
	<dependency>
		<groupId>com.sun.tools.btrace</groupId>
		<artifactId>btrace-client</artifactId>
		<version>${btrace.version}</version>
	</dependency>
</dependencies>

<repositories>
	<repository>
		<id>btrace-repo</id>
		<name>btrace-repo</name>
		<url>https://dl.bintray.com/btraceio/maven/</url>
		<releases>
			<enabled>true</enabled>
		</releases>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
</repositories>

需要注意的是,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 上午
 */
@BTrace
public 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.java
com.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 下午
 */
@BTrace
public 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.java
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, ]
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 下午
 */
@BTrace
public 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.java
com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
com.gitee.funcy.jtools.btrace.HoldNetTask.visitWeb:27
com.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 下午
 */
@BTrace
public 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.java
1000 msec:27654
1000 msec:28647
Thread[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 下午
 */
@BTrace
public 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.java
java.net.URL.openConnection
http://www.sina.com.cn
java.protocol.handler.pkgs
www.sina.com.cn
===============

这里监控URL对象,在调用openConnection()方法时,获得当前对象实例self,然后获取对应实例的host等属性。

6. 注意事项

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

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

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

免责声明:本站发布的内容(图片、视频和文字)以原创、来自互联网转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系QQ:712375056 进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。