我是从淘宝开发人员的博客里了解到有BTrace这样一个工具的。最近自己也用它来解决了一个小问题,确实觉得有用,没事干,就把官方的用法指南翻译出来。
版本 1.2 (20101020)
BTrace 是一个可靠的,用来动态跟踪Java程序的工具。它通过动态对运行中的Java程序进行字节码生成来工作。BTrace会对运行中的Java程序的类插入一些跟踪操作 来对被跟踪的程序进行热替换。
BTrace 名词
探测点 (Probe Point)
就是一系列的跟踪语句被执行的“地方”或者“事件”。探测点就是我们想要执行一些跟踪语句的地方或者事件。
跟踪动作或简称动作 (Trace Actions)
就是那些当探测点被触发时所执行的跟踪语句。
动作方法
BTrace的跟踪语句是必须定义在一个类的某个静态方法里的,这个静态方法就叫“动作”方法。
BTrace 程序结构
一个BTrace程序就是一个普通的Java类,这个类至少有一个这样的方法:
public static void ***
其次这个方法还需要加上BTrace相关的注解。这些注解用来指明被跟踪程序的“位置”(也就是前面提到的探测点)。跟踪动作需要在这个静态方法的方法体里指定。这些(注意,可以有多个)静态方法就是所谓的“动作”方法。
BTrace的限制
为了保证跟踪动作是“只读”的(也就是这些动作不可以修改被跟踪程序的状态)和有限度的(比如在固定时间里结束)。一个BTrace程序只允许完成一些指定的动作。下面是BTrace一些不可以完成的事情:
- 不能创建新的对象
- 不能创建新的数组
- 不能抛出异常
- 不能捕获异常
- 不能进行任何的实例函数或者静态函数 -- 只有com.sun.btrace.BTraceUtils类中的静态函数或者BTrace程序自己声明的
- 函数才可以被BTrace调用
- 对1.2版本以前的程序,不能由实例级别的field和函数。只有静态公开的并且无返回值的函数才允许在BTrace类中使用。所有
的field必须是静态的。
- 不可以在目标程序的类,或者对象的静态或者实例级别的field进行赋值。但是,BTrace自身的类是可以给它的静态field进行赋值的。
- (也就是意味着跟踪的状态时可以更改的)
- 不能有outer,inner,嵌套的或者本地类。
- 不能有同步代码块或者同步的函数
- 不能有循环语句(for,while, do..while)
- 不能继承其它类(父类只能是java.lang.Object)
- 不能实现接口
- 不能包含断言(assert)语句
- can NOT use class literals (这个我也没搞明白是啥意思)
一个简单的BTrace程序(适用于版本1.2)
一个简单的Brace程序 (适用于1.2以前的版本)
以上的程序都可以对一个正在运行的Java进行跟踪。这个程序会在目标程序调用Thread.start()函数来启动一个线程时打印出“about to start a thread!".还有其他一些有趣的探测点,比如,我们可以一个函数返回的时候插入一段探测动作,或者是一个函数碰到异常时也插入一段,在函数里用到的field,对象和数组的创建,代码行号等等。参考
@OnMethod和其它注解来了解更多细节。
运行BTrace的步骤
- 找到你要跟踪的Java进程的ID。可以用JDK自带的jps程序找到你要的进程id。
- 编写一段BTrace程序 -- 你也可以修改我们的样例程序
- 运行btrace工具 (到官方网站下载最新的压缩包解压,在bin目录就有这个文件,注意如果你是在windows环境下,那么这个文件的名字应该是btrace.bat):
btrace <pid> <btrace-script>
注意: BTrace只支持JDK 6以上的环境
BTrace 命令行
BTrace 通过使用下面的btrace命令行工具来运行:
btrace [-I <include-path>] [-p <port>] [-cp <classpath>] <pid> <btrace-script> [<args>]
其中各个参数的含义如下:
include-path : 是一些用来查找头文件的目录。BTrace
- port: BTrace代理程序所侦听的端口,这是可选的选项。默认是2020
- classpath: 是一些用来查找jar文件的目录。默认是".",也就是当前目录
- btrace-script: 就是跟踪程序本身。如果这是个java文件,那么在执行前会进行编译。否则就被认为是已经编译好的
- 程序(比如可能是个.class文件),而直接运行
- args: 这是传递给BTrace程序的参数。BTrace程序可以通过内置的$符号来引用这些参数,$length是这些参数的个数。
编译BTrace脚本
使用btracec脚本,我们可以对BTrace程序进行编译。btracec就是类似javac那样的程序,输入是一个BTrace程序,输出时一个.class文件。
btracec [-I <include-path>] [-cp <classpath>] [-d <directory>] <one-or-more-BTrace-.java-files>
各个参数的含义如下:
- include-path
- classpath:
- directory:
使用BTrace代理来启动一个目标程序
到目前为止,我们已经知道如何跟踪一个正在运行的Java程序。我们甚至可以通过BTrace代理来启动目标程序。如果你想跟踪目标程序开始的时候作了什么事情,你就需要通过BTrace代理来启动它,并制定对应的跟踪脚本。下面的命令就是叫你如何作到这点的。需要注意的是:这里制定的跟踪脚本必须是已经编译好的(就是.class文件)。
java -javaagent:btrace-agent.jar=script=<pre-compiled-btrace-script1>[,<pre-compiled-btrace-script1>]* <MainClass> <AppArguments>
以这种方式启动的目标程序,会把跟踪输出到当前目录下一个叫作<btrace-class-file-name>.btrace的文件中。如果你不想这个目标程序给其他的远程BTrace客户端使用,那么可以指定noServer=true这个参数给BTrace代理。BTrace的发布目录下有个叫
btracer的脚本就是专门作上面的事情的:
btracer <pre-compiled-btrace.class> <application-main-class> <application-args>
支持的参数
- bootClassPath - 启动时用到的classpath
- systemClassPath - 系统classpath
- debug - 是否输出详细的调试日志(true则输出,false则不输出)
- unsafe - 是否不检查是否违反了btrace限制 (true则不检查,false则检查)
- dumpClasses - 是否把二进制码dump到文件中(true/false)
- dumpDir - dump出来文件放到这个目录下
- stdout - 是否把btrace输出重定向到标准输出(true/false)
- probeDescPath - 存放探测点描述文件的路径
- script - 脚本的路径,当代理启动时会运行这个脚本
- scriptdir - 脚本所在的目录,当代理启动时会运行这个目录下的脚本
- scriptOutputFile - 输出文件的路径,btrace代理会把输出写到这个文件中
重要的系统属性
btrace.agentname- 用来区分同一台机器上运行着的不同的btrace代理。
BTrace的注解
方法注解
@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.OnEvent 这个注解用来跟踪函数与"外部”的事件关联起来。当BTrace客户端发送了一个“事件”后,这个注解里的操作就会被执行。客户端发送的事件可能是由用户触发的(比如按下Ctrl-C)。事件的名字是个字符串,这样跟踪操作就只会在对应的事件触发后被执行。到目标为止,BTrace命令行客户端会在用户按下Ctrl-C后发送事件,参考例子HistoOnEvent.java来了解用法。
@com.sun.btrace.annotations.OnProbe 这个注解可以用来避免使用BTrace脚本的内部类。@OnProbe探测点被映射到一个或多个@OnMethod上。目前这个映射是通过一个XML探测描述文件类指定的(这个文件会被BTrace代理所使用)。参考例子SocketTracker1.java和对应的描述文件java.net.socket.xml.当运行这个例子时,xml文件需要放在目标JVM所有运行的目录下(或者修改btracer.bat中的probeDescPath选项来指向任意的xml文件)。
参数相关的注解
1.2版本以后,有一个布尔型的参数fqn可以用来执行是否获取函数的全名称
1.2版本以后,有一个布尔型的参数fqn可以用来执行是否获取函数得到全名称
无注解的参数
没有注解的BTrace探测函数参数是用来作签名匹配的,因为他们必须必须在固定的位置上出现。然而,它们可以和其他的注解的参数进行交换。如果一个参数的类型是*AnyType[]*,它就会“吃”掉所所有剩下的参数。没有注解的参数的具体含义与他们所在的位置有关:
- Kind.ENTRY, Kind.RETURN- 探测函数的参数
- Kind.THROW - the thrown exception
- Kind.ARRAY_SET, Kind.ARRAY_GET - 数组的索引
- Kind.CATCH - 被捕获的异常
- Kind.FIELD_SET - field的值
- Kind.LINE - 源代码行号
- Kind.NEW - 类名
- Kind.ERROR - 抛出去的异常
字段相关的注解
@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
类相关的注解
DTrace集成
现在很少人用Solaris, 所以这段就略过啦,吼吼。
BTrace例子
例子的简短说明: