java线上排查神器:btrace使用示例
问题背景
最近我的一个正在生产环境运行的程序出现了故障,问题简单描述一下就是:这个程序需要实现任务调度,程序设置有最大运行数量的限制,如果一个任务失败了,那么这个任务在之后就会被重新调度,但是实际情况是这个失败了的任务并没有被重新调度,而是卡死了。
在阅读代码之后,发现需要打印程序中一个map类型的类属性的内容来定位最终的问题所在。但是,这种bug只发生在负载较大的时候,所以非常难复现。如果把程序停止,然后修改代码,运行新的程序,再想要复现这个bug就很困难了。
在查阅资料后,我发现了btrace这个工具可以完美解决我的需求。
BTrace简介
这个工具的官方Github仓库链接为:https://github.com/btraceio/btrace
官方对于这个工具的描述如下:
BTrace is a safe, dynamic tracing tool for the Java platform.
BTrace can be used to dynamically trace a running Java program (similar to DTrace for OpenSolaris applications and OS). BTrace dynamically instruments the classes of the target application to inject tracing code ("bytecode tracing").
简单而言,这个工具可以把一段代码动态的插入到一个正在运行的Java程序里,而无需停止这个程序。其思想有点类似于动态插桩。关于BTrace的具体原理后面我会写一篇文章来解析一下。
但是,插入的代码是会受到一定的限制的,其中对本次编程影响最大的就是:方法调用只能有对BTraceUtils里面的方法的调用,如果有对其他方法的调用,就会报如下所示的错误:
使用BTrace
BTrace的具体使用这里就不赘述了,网上有很多博客介绍了这个工具的基本使用方法。(需要注意的是,网上很多文章代码里面引用的包已经过时了,如果遇到引用包错误的问题,建议看看btrace给的samples里面的代码,可以在这个里面的代码的基础上做自己的开发)
以下主要介绍一下怎样编写BTrace脚本来打印程序中的类属性。主要的思想是使用反射机制,BTrace脚本被注入之后,就和程序在同一个进程空间里了,那么就可以通过反射机制来获取到目标类的Class,然后获取到Field,最后通过对象来获取到这个类属性的值。具体的实现代码如下:
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.Injected;
import org.openjdk.btrace.core.annotations.OnMethod;
import org.openjdk.btrace.core.annotations.ProbeClassName;
import org.openjdk.btrace.core.annotations.ProbeMethodName;
import org.openjdk.btrace.core.annotations.Self;
import org.openjdk.btrace.core.annotations.ServiceType;
import org.openjdk.btrace.services.impl.Printer;
import org.openjdk.btrace.core.BTraceUtils;
import java.lang.reflect.Field;
import java.util.Map;
/**
* This script traces method entry into every method of
* every class in javax.swing package! Think before using
* this script -- this will slow down your app significantly!!
*/
@BTrace
public class WatchMapTrace {
@OnMethod(
clazz = "com.server.download.SubTaskDistributeCenter",
method = "autoDistribute"
)
public static void m(@Self Object o, @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) {
Class c=BTraceUtils.classForName("com.qzero.server.download.SubTaskDistributeCenter",BTraceUtils.contextClassLoader());
Field f=BTraceUtils.field(c,"threadMap",true);
Map map=(Map)BTraceUtils.get(f,o);
BTraceUtils.print(map+"");
// Get the element of map
Thread thread=(Thread)BTraceUtils.get(map,109579);
BTraceUtils.print("Thread status : "+BTraceUtils.threadState(thread));
}
}
代码是基于samples里面的AllMethods.java改的,其中的OnMethod注解指定了需要被插桩的方法,Self注解可以获取到含有这个方法的对象。
这里比较麻烦的点就是,btrace不允许除BTraceUtils以外的方法调用,不过好在BTraceUtils里面也给出了很多常用的方法,例如反射相关的,还有操作Map相关的,具体可以查看BTraceUtils.java的源码,链接如下:https://github.com/btraceio/btrace/blob/develop/btrace-core/src/main/java/org/openjdk/btrace/core/BTraceUtils.java
把这段脚本使用BTrace加载到正在运行的程序上,等到autoDistribute再次被调用时,程序里就会打印threadMap里面的内容了,我也是由此定位到了问题所在。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南