背景
生产环境曾运行在 JDK 8。线上出现问题时,用 BTrace attach 到运行中的 JVM:按条件过滤慢方法(例如耗时超过 9ms),持续输出,不改代码、不重启。
当时 JFR 仍是商业特性,attach 式动态追踪是可行且低成本的方案之一。本文先记录 JDK 8 时代的诉求与 BTrace 用法,其它 JDK 版本下的工具对照见文末章节。
JDK 8 时代 BTrace 在解决什么
从 BTrace 使用笔记 中的脚本可以看出,核心需求是:
- attach 到运行中的 JVM,不重启
- 按条件过滤慢方法(例如大于 9ms)
- 持续输出,而不是只看一次快照
- 必要时查看参数、返回值、调用栈
示例脚本(慢调用过滤 + 计数):
@OnMethod(clazz = "com.wiloon.package0.Class0", method = "method0",
location = @Location(Kind.RETURN))
public static void printMethodRunTime(@ProbeClassName String probeClassName,
@Duration long duration) {
long d = duration / 1000000;
if (d > 9) {
i++;
println("index: " + i + ", timestamp:" + timestamp("HH:mm:ss")
+ ", " + probeClassName + ", duration: " + d + " ms");
}
}
执行方式:
jcmd -l
/bin/btrace <PID> MethodDuration_redis.java
Attach 原理(简述)
BTrace 通过 JDK Attach API 把 agent JAR 加载进目标 JVM,在 agentmain 里注册 ClassFileTransformer,用 ASM 织入探针并对已加载类 retransform。客户端经 Socket 下发脚本、回收输出。细节见 BTrace 中的「Attach 原理」一节。
JDK 8 下的结论
在 JDK 8 生产环境中,针对「不重启、按条件持续盯某个方法」这类问题,BTrace 是合理且常用的选择。脚本需遵守 BTrace 的安全限制,上线前在本地充分验证。
参考(JDK 8 相关)
其它 JDK 版本
以下供 JDK 升级后的选型参考;不再以 JDK 8 为前提。
JDK 11 及以后:JFR 与 BTrace 2.x
从 JDK 11 起 JFR 免费,许多 GC、锁、分配类问题可用 jcmd <pid> JFR.start 等命令处理,见 Java Flight Recorder。
BTrace 2.x 仍在维护,支持 oneliner,文档写明兼容 Java 8–25(更高版本需实测):
btrace -n 'com.myapp.*::* @return if duration>100ms { print method, duration }' <PID>
JDK 21 及以后:动态 Agent 加载
attach 动态 agent 时,目标 JVM 通常需要:
-XX:+EnableDynamicAgentLoading
否则可能失败或告警。勿使用 -XX:+DisableAttachMechanism。
Java 25 / 26:JDK 自带能力与工具分工
| 需求 | 更常见的做法 |
|---|---|
| 谁在吃 CPU | async-profiler(asprof -e cpu --jfrsync)或 JFR |
| GC、锁、分配、虚拟线程 pinning | JFR |
| 临时看方法参数、返回值、耗时 | Arthas watch / trace / monitor |
| 线程卡死、CPU 飙高 | Arthas thread + jcmd / jstack |
| 持续、带条件的慢调用统计 | JFR 持续录制,或 BTrace 脚本 |
Java 25 引入实验性 jdk.CPUTimeSample(JEP 509),用 CPU 时间采样;目前主要面向 Linux,尚不能替代 async-profiler 的全部能力。
BTrace 与 Arthas 的分工
| 维度 | Arthas | BTrace |
|---|---|---|
| 交互方式 | 命令行,即查即看 | 写脚本,持续运行 |
| 上手成本 | 低 | 高 |
| 适用问题 | 「现在出了什么事?」 | 「持续多久、频率多高、满足什么条件?」 |
| 条件过滤 + 累计统计 | 弱 | 强 |
追踪方法内对特定类的 new |
做不到 | 可以 |
对应当年慢调用脚本的现代写法:
# 一次性定位(Arthas)
trace com.wiloon.package0.Class0 method0 '#cost>9'
watch com.wiloon.package0.Class0 method0 '{params,returnObj,#cost}' '#cost>9'
# 持续计数(BTrace oneliner)
btrace -n 'com.wiloon.package0.Class0::method0 @return if duration>9ms { print method, duration }' <PID>
概括:Arthas 像手电筒,BTrace 像监控摄像头。
升级后的选型建议(2026)
第一层(减少临时 attach):
- 应用指标:Micrometer / Prometheus
- 分布式追踪:OpenTelemetry
- JVM 基线:JFR 模板或周期性
jcmd JFR.dump
第二层(出问题时 attach,不重启):
- Arthas — 快速看线程、慢方法、参数、调用栈
- JFR — GC、锁、分配、虚拟线程
- async-profiler — CPU 热点、火焰图
第三层(BTrace 仍适合):
- 长时间、带条件、可累计的探针
- 追踪特定类的对象分配
- 自定义输出到文件或 StatsD
- Arthas 表达不了的复杂逻辑
升级后的结论
在 Java 26 等较新 JDK 上,BTrace 不是解决同类问题的默认最佳实践;标准答案更接近 JFR + async-profiler + Arthas,再叠加 metrics / tracing。
BTrace 仍有效,尤其「慢调用过滤 + 持续采集」,但更像专项工具。若保留 JDK 8 时代的脚本库,建议:少数持续监控脚本留 BTrace;临时排查迁 Arthas;性能分析迁 JFR + async-profiler;在新 JDK 上实测 BTrace 2.2.x 与 -XX:+EnableDynamicAgentLoading。
场景对照
| 场景 | JDK 8(BTrace) | 较新 JDK |
|---|---|---|
| 慢方法过滤 | @OnMethod + @Duration |
Arthas trace / watch,或 BTrace oneliner |
| CPU 热点 | 不擅长 | asprof -d 30 -e cpu --jfrsync profile.jfr <pid> |
| GC 与延迟 | 需自定义脚本 | JFR default.jfc |
| 看方法参数 | BTrace 脚本 | Arthas watch |