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 {
@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);
}
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);
}
abstract class Client implements ClassFileTransformer, CommandListener {
static {
ClassFilter.class.getClass();
ClassReader.class.getClass();
ClassWriter.class.getClass();
...
}
private byte[] instrument(Class clazz, String cname, byte[] 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内存中的类字节码,达到注入代码的目的。