培养舱实况:一个 Java Agent 正在监视这个服务

Go/Node 把程序编译成「罐头」,Java 把程序养在「培养舱」里。这个页面就是培养舱的观察窗: 一个核心插桩不足百行的 Java Agent(ByteBuddy 插桩)挂在正在服务这个页面的后端进程上, 把方法调用(耗时 ≥ 10µs)的耗时、线程、异常记下来——业务代码一行都没改。

实时采集 · 每 2 秒拉取增量

现场:agent 的自报家门

这些数字来自 agent 自身的 /agent/stats 接口——它甚至统计了自己过滤掉了多少噪声调用。

已插桩业务类
—
不改一行代码拿到的
已记录方法调用
—
耗时 ≥ 10µs 才记
过滤掉的噪声
—
耗时 < 10µs 的调用,多来自启动期分词
缓冲丢弃
—
缓冲只有 4096 条,写满时最旧的让位
提示:点「发一次真实请求」,几秒后下面的时间轴上就会出现这次调用在服务端留下的脚印。

时间轴:方法调用跑在了哪个线程上

每条泳道是一条线程,横条是一次方法调用(位置 = 发生时刻,宽度 = 耗时)。 反应式服务里线程名信息量爆炸:蓝色是 Netty 事件循环(请求响应的主战场),橙色是 boundedElastic(兜住阻塞操作)。

时间轴还没有数据——agent 刚记下的调用会自动出现在这里。
reactor-http-nio(事件循环) boundedElastic(阻塞兜底池) main 抛了异常的调用

明细:最近的方法调用

每个事件都带完整坐标:类、方法、耗时、线程、嵌套深度。这就是 APM 链路追踪的原始形态。

时刻方法耗时线程深度
暂无数据

原理:agent 是怎么「不碰代码」改掉代码的

全部魔法集中在类加载的一瞬间,磁盘上的 jar 和源码仓库自始至终没有被打开过。

1. 内联插桩

agent 注册一个 ClassFileTransformer;每个类被加载时,JVM 把原始字节码递给它, ByteBuddy 用 @Advice 把「进入记时间 / 退出算耗时」几行指令原地拷贝进方法体。 不生成代理类、不改方法签名。

2. shade + relocate

ByteBuddy 自身被改名打包进 agent jar(shaded.bytebuddy), 与业务 classpath 上任何版本的 byte-buddy 物理隔离——这是所有生产级 agent(OTel、Arthas)的标准姿势。

3. 数据回流

事件写进进程内的环形缓冲,agent 自带一个只绑定 127.0.0.1 的 HTTP 接口; 对外只靠一个两个 GET 方法的转发 controller。读者把 agent 挂到任何 Spring 项目上,同样三步就能复现。

对照实验:在本页地址后面加上 ?api=http://localhost:8080(指向你本地服务), 再给你的服务挂上 trace-agent.jar(仓库 trace-agent/ 目录,./mvnw package 一条命令构建), 你自己的服务也能被这样「开天眼」。 SkyWalking / OpenTelemetry 的 javaagent 就是这个机制的产品化。