线下的应用遇到问题时我们可以通过改代码加日志追踪或者通过远程调试来获取运行时的程序细节, 但如果应用一开始没有开启远程调试的端口, 那你要修改 JVM 参数添加调试的参数然后重启应用, 这种对于线上应用来说就不适用了, 线上应用不允许随便修改代码也无法远程调试.BTrace 就是一个可以在不改代码,不重启应用的情况下,动态的查看程序运行细节的工具.这篇文章主要介绍 BTrace 的使用场景以及如果用 BTrace 来输出程序的执行细节.
安装 BTrace
官方下载地址: btrace
下载之后设置 BTRACE_HOME 环境变量, 并把 BTACE_HOME/bin 添加到 PATH 环境变量中:
export BTRACE_HOME=/i/software/btrace-bin-1.3.9
export PATH=$BTRACE_HOME/bin:$PATH
Btrace 的使用场景
查看某一个方法中入参和返回值
查看某一个方法的响应时间
查看某一个方法中所有外部调用的响应时间,方便定位方法响应慢的具体位置及原因
打印方法调用的线程堆栈
如何使用 BTrace
使用起来比较简单,只需要执行
btrace {javaPid} {btrace脚本}
就行,如果要把输出调试信息到控制台, 要加上 - v 参数,-o 参数可以把结果输出到文件中.
如何编写 BTrace 脚本
BTrace 脚本编写比较简单, 主要用到 BTraceUtils 工具类提供的方法, 具体的教程可以参考 Btrace 使用教程 , 在编写脚本时, 最好把 BTrace 提供的 Jar 包引入到 Build Path 上,方便在编译期发现语法错误,BTrace 的三个 jar 包在安装包的 build 目录下, 如果你用的是 Maven 来构建项目, 可以添加如下依赖,把 btrace 的路径对应修改即可:
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-agent</artifactId>
<version>1.3.9</version>
<scope>system</scope>
<systemPath>I:/software/btrace-bin-1.3.9/build/btrace-agent.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-boot</artifactId>
<version>1.3.9</version>
<scope>system</scope>
<systemPath>I:/software/btrace-bin-1.3.9/build/btrace-boot.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-client</artifactId>
<version>1.3.9</version>
<scope>system</scope>
<systemPath>I:/software/btrace-bin-1.3.9/build/btrace-client.jar</systemPath>
</dependency>
我自己写了一个 StringTableTest 类,这个类有个 intern 方法,我想用 btrace 脚本来追踪 intern 方法的入参和返回值,并把调用堆栈打印出来, StringTableTest 类源码如下所示,为了让 BTrance 能够追踪到该进程,我先让主线程休眠了 10 秒钟:
package com.ezlippi;
import java.util.concurrent.TimeUnit;
public class StringTableTest {
public static void main(String[] args) {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
StringTableTest test = new StringTableTest();
long ms = test.intern(1000000);
System.out.println(ms);
}
public long intern(int times) {
long start = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
String.valueOf(i).intern();
}
return System.currentTimeMillis() - start;
}
}
BTrace 脚本源码如下所示,第一个方法用于打印方法的参数,返回值和执行的时间点,第二个方法用于打印是否调用了 StringTableTest 的第 22 行.
package com.ezlippi.performanace;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TraceMethodArgsAndReturn {
@OnMethod(
clazz="com.ezlippi.StringTableTest",
method="intern",
location=@Location(Kind.RETURN)
)
public static void traceExecute(int sSrc,@Return long result){
println("invoke StringTableTest.intern");
println(strcat("input arg is:", str(sSrc)));
// 打印函数返回结果result
println(strcat("result is:",str(result)));
//打印方法执行时间点
println(strcat("time is:",str(timeMillis())));
//打印线程堆栈
jstack();
}
@OnMethod(
clazz="com.ezlippi.StringTableTest",
location=@Location(value=Kind.LINE,line=22)
)
public static void traceExecute(@ProbeClassName String pcn,@ProbeMethodName String pmn,int line){
println(strcat(strcat(strcat("call ",pcn),"."),pmn));
}
}
需要注意的是,如果方法参数是原生数据类型, 那 traceExecute 方法的参数类型要与之匹配,不用用对象类型,如果参数是对象,可以用 AnyType 来表示,对于返回值也是类似,如果返回值为对象类型,可以用 Object 表示.
首先执行 StringTableTest 的 main 方法,然后通过 jps 命令获取 jvm 进程的 Pid,最后调用
btrace 13440 ./scripts/TraceMethodArgsAndReturn.java
就会打印执行结果, 我的当前目录是在 btrace 的 bin 目录下,所有脚本放在 scripts 目录下,BTrace 官方也提供了很多样例,在 samples 目录下,
我把整个过程写成了一个脚本 start.sh,只需要先在 IDEA 里执行 main 方法后,执行 start.sh 即可:
#!/bin/bash
currentDir="$(dirname $0)"
export BTRACE_HOME=$currentDir/../
export PATH=$BTRACE_HOME/bin:$PATH
pid="$(jps | grep "StringTableTest"|cut -d " " -f1)"
btrace $pid $currentDir/scripts/TraceMethodArgsAndReturn.java
最后输出结果如下所示:
call com.ezlippi.StringTableTest.intern
invoke StringTableTest.intern
input arg is:1000000
result is:628
time is:1512899327404
//线程堆栈
com.ezlippi.StringTableTest.intern(StringTableTest.java:22)
com.ezlippi.StringTableTest.main(StringTableTest.java:13)
有了 BTrace 之后,最终线上应用的运行细节就更方便了.
来源: https://juejin.im/entry/5a66d53ff265da3e3304790f