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 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内存中的类字节码,达到注入代码的目的。
时间: 2024-12-09 01:27:09