BTrace 是什么
BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务。写好脚本,直接用命令执行即可,不用动原程序的代码。
原理
总体来说,BTrace 是基于动态字节码修改技术(Hotswap)来实现运行时 java 程序的跟踪和替换。大体的原理可以用下面的公式描述:Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + JDK6 Instumentation) + Socket
其实 BTrace 就是使用了 java attach api 附加 agent.jar ,然后使用脚本解析引擎+asm来重写指定类的字节码,再使用 instrument 实现对原有类的替换。
安装和配置
本次安装和配置在 Linux Ubuntu 14.04 下进行。目前 BTrace 的最新版本为 1.3.9,代码托管在 [github] 上。第一步,在github 上下载 releases 版 btrace-bin-1.3.9.tgz,zip 版的没有 build 目录。第二步,解压 btrace-bin-1.3.9.tgz 到一个目录即可,例如 /home/fengzheng/soft/btrace
, 到这一步其实就可以用了,只是执行脚本的时候需要在 btrace 命令前加上绝对路径,如果想在任意目录可执行,进行下一步第三步,配置环境变量,配置的环境变量包括 JAVA_HOME
和 BTRACE_HOME
,例如我的配置如下:
export JAVA_HOME=/home/fengzheng/soft/jdk1.8.0_111
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
export BTRACE_HOME=/home/fengzheng/soft/btrace
export PATH=$PATH:$BTRACE_HOME/bin
之后执行命令 source /etc/profile
,使环境变量立即生效。接下来在任意目录执行 btrace
命令,都可以执行成功了。
简单测试用例
btrace 最简单的语法是 btrace $pid script.java
,所以需要知道要探测的 Java
程序的进程id,然后编写一个探测脚本即可。
1. 写一个常驻内存的 Java
程序,这里写了一个无限循环,每隔5秒钟输出一组计算结果,内容如下:
package kite.lab.utils;
/**
* NumberUtil
*
* @author fengzheng
* @date 2017/2/15
*/
public class NumberUtil {
public int sum(){
int result = 0;
for(int i = 0; i< 100; i++){
result += i * i;
}
return result;
}
public static void main(String[] args){
while (true) {
Thread.currentThread().setName("计算");
NumberUtil util = new NumberUtil();
int result = util.sum();
System.out.println(result);
try {
Thread.sleep(5000);
}catch (InterruptedException e){
}
}
}
}
顺便说一下命令行编译和运行 Java 的过程:
编译:javac -d . NumberUtil.java
,定位到 NumberUtil.java 所在目录,然后执行此命令行,将会在当前目录(.表示当前目录)生成包名所示的目录结构,kite/lab/utils/NumberUtil.class
执行:java kite.lab.utils.NumberUtil
即可
2. 执行上面的程序后,可用 jps
命令查看 pid(一般情况下用哪个账号启动的程序,就要用哪个账号执行 jps ,root 账号除外),执行 jps 命令看到如下结果:
root@ubuntu:/home/fengzheng/codes/btrace# jps
10906 Jps
10860 NumberUtil
3. 可以看到刚刚执行的 java 进程为 10860
4. 编写 btrace 脚本,脚本内容简单如下:
package kite;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.Strings.strcat;
import static com.sun.btrace.BTraceUtils.jstack;
import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;
/**
* NumberUtilBTrace
*
* @author fengzheng
* @date 2017/6/20
*/
@BTrace
public class NumberUtilBTrace {
@OnMethod(
clazz="kite.lab.utils.NumberUtil",
method="sum",
location=@Location(Kind.RETURN)
)
public static void func(@Return int result) {
println("trace: =======================");
println(strcat("result:", str(result)));
jstack();
}
}
意思是在执行结束后(location=@Location(Kind.RETURN) 表示执行结束)输出结果和堆栈信息
5. 预编译:执行之前可以用预编译命令检查脚本的正确性,预编译命令为 btracec,它是一个 javac-like 命令,btracec NumberUtilBTrace.java
6. 调用命令行执行,btrace 10860 NumberUtilBTrace.java ,(如果要保存到本地文件中,可以使用转向命令 btrace 10860 NumberUtilBTrace.java > mylog.log )打印的信息如下
trace: =======================
result:328350
kite.lab.utils.NumberUtil.sum(NumberUtil.java:16)
kite.lab.utils.NumberUtil.main(NumberUtil