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

来源:互联网 发布:2016网络春晚 编辑:程序博客网 时间:2024/04/30 06:19

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=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内存中的类字节码,达到注入代码的目的。


0 1
原创粉丝点击