首页 > 代码库 > 开发工具系列(一):Btrace——线上Debug工具

开发工具系列(一):Btrace——线上Debug工具

Btrace

Btrace用于调试正在运行的系统,并且在调试时不会暂停系统。特别适用于跟踪线上问题。你可以实时监控一个系统中任何一个方法的调用,你可以知道这些方法的参数、返回值是什么,还可以知道方法调用消耗了多少时间。

Btrace不需要安装,只要下载一个包,解压即可。

Btrace用法为bin/btrace <pid> <trace-script>。其中pid是正在运行的java进程,trace-script是跟踪脚本,它其实就是一段java代码。

Hello World

首先我们模拟一个正在运行的程序,它仅有一个循环。

package com.caipeichao;
 
public class NullApp {
 
    public static void main(String[] argv) {
        new NullApp().run();
    }
 
    public void run() {
        for (int i = 0; i < 100000; i++) {
            sleep(1000);
            new MyObj().life(i);
        }
    }
 
    private static class MyObj {
 
        public void life(int n) {
            System.out.println(n);
        }
    }
 
    private void sleep(int n) {
        try {
            Thread.sleep(n);
        } catch (InterruptedException e) {
        }
    }
}

然后开启这个程序: java com.caipeichao.NullApp

通过jps命令得到这个程序的PID,这里为13348。

> jps
3034 RemoteMavenServer
2902 Main
15147 Jps
13348 NullApp

准备工作做完了,现在编写最重要的跟踪脚本。

import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
 
@BTrace
public class HelloBtrace {
  // 当com.caipeichao.NullApp.sleep方法返回时,执行该方法 
  @OnMethod(clazz="com.caipeichao.NullApp",
    method="sleep",
    location=@Location(Kind.RETURN))
  public static void onSleep() {
    println("Hello world");
  }
}

运行btrace,得到如下输出。

> btrace 13348 HelloBtrace.java
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world

常用注解

名称作用域作用
@BTrace声明跟踪脚本
@OnMethod(clazz,method,location)方法当指定方法被调用时
@OnMethod(method="<init>")方法当构造函数被调用时
@OnMethod(clazz="/java\\.io\\..*Input/"))方法方法名称正则匹配
@Location(kind)@OnMethod指定监控方法调用前还是调用后
@Location(value=http://www.mamicode.com/Kind.NEWARRAY, clazz="char")@OnMethod监控新增数组
@Self参数表示被监控的对象
@ProbeMethodName参数被监控的方法名称
@ProbeClassName参数被监控的类名
@OnTimer(interval)方法定时调用某个方法
@OnLowMemory(pool,threshold)方法当内存不足时
@OnExit方法当程序退出时
@OnProbe(namespace="java.net.socket",name="bind")方法监控socket中的bind方法

常用方法

方法作用
println在本地控制台输出一行
print在本地控制台输出
printArray在本地控制台输出数组
jstack打印远程方法的调用调用栈
jstackAll输出所有线程的调用栈
exit退出跟踪脚本
Strings.strcat连接字符串
Reflactive.name获取类名
Threads.name线程名
Threads.currentThread当前线程
deadlocks打出死锁线程
sizeof获取对象的大小,比如List对象就返回List.size()
Sys.Env.property获取系统变量

原理

BTrace利用了java.lang.instrument包实现代码注入。首先通过VirtualMachine.attach(pid)连接远程JVM,然后通过VirtualMachine.loadAgent("*.jar")加载一个btrace的jar包。这个jar包最重要的代码如下。

public static void premain(String args, Instrumentation inst) {
  main(args, inst);
}
 
public static void agentmain(String args, Instrumentation inst) {
  main(args, inst);
}
 
// 将btrace的jar包添加到ClassLoader搜索目录 
private static synchronized void main(final String args, final Instrumentation inst) {
  ...
  inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(path)));
  ...
  inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
  ...
  startServer();
}
 
// 开启服务 
private static void startServer() {
  ...
  while (true) {
    try {
      ...
      handleNewClient(client);
    } catch (RuntimeException re) {
      if (isDebug()) debugPrint(re);
    } catch (IOException ioexp) {
      if (isDebug()) debugPrint(ioexp);
    }
  }
}
 
// 修改内存中的类定义 
private static void handleNewClient(final Client client) {
  ...
  inst.addTransformer(client, true);
  ...
  inst.retransformClasses(classes);
}
 
// 用ASM动态生成字节码 
abstract class Client implements ClassFileTransformerCommandListener {
  static {
    ClassFilter.class.getClass();
    ClassReader.class.getClass();
    ClassWriter.class.getClass();
    ...
  }
 
  private byte[] instrument(Class clazzString cnamebyte[] target) {
    byte[] instrumentedCode;
    try {
    ClassWriter writer = InstrumentUtils.newClassWriter(target);
    ClassReader reader = new ClassReader(target);
    Instrumentor i = new Instrumentor(clazz, className,  btraceCode, onMethods, writer);
    ...
  }
}

一句话总结,btrace利用instrument工具修改JVM内存中的类字节码,达到注入代码的目的。


开发工具系列(一):Btrace——线上Debug工具