目录

SkyWalking 源码复盘 ①:-javaagent 到 premain

一、-javaagent 和普通依赖有什么区别

二、SkyWalking Agent 的入口类

三、premain() 主要干了哪几件事

四、第一步:加载配置

五、第二步:加载插件

六、第三步:安装字节码转换器

七、什么是 Instrumentation

八、第四步:启动 Agent 内部服务

九、结合你的 order-service 看完整启动过程

十、为什么不是所有类都增强

十一、这一节最核心的认知

十二、面试回答模板

十三、本节自测


这一轮不从几十万行源码硬啃,而是沿着你实际启动 order-service 的那条链往下追。

你在 IDEA 中配置的是:

-javaagent:H:\tools\...\agent\skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=127.0.0.1:11800

这三个参数分别解决:

-javaagent                    启动 SkyWalking Agent
service_name                  当前 JVM 在 UI 中叫什么
collector.backend_service     Trace 数据发给哪个 OAP

一、-javaagent 和普通依赖有什么区别

普通 Maven 依赖:

<dependency>
    ...
</dependency>

只是把类放入当前应用的 ClassPath,通常要由业务代码主动调用。

而:

-javaagent:skywalking-agent.jar

是告诉 JVM:

在应用程序 main() 执行前,先启动这个 Java Agent。

Java Agent 的标准入口是:

public static void premain(
        String agentArgs,
        Instrumentation instrumentation
)

Byte Buddy 官方也把 premain 描述为 Java Agent 的入口,并通过 Instrumentation 接入 JVM 的类转换机制。(GitHub)

启动顺序可以理解为:

启动 JVM
  ↓
识别 -javaagent
  ↓
加载 skywalking-agent.jar
  ↓
调用 SkyWalkingAgent.premain()
  ↓
安装字节码增强器
  ↓
启动 Spring Boot main()

所以 SkyWalking 能在 Tomcat、Spring MVC、JDBC 等类加载时提前介入。


二、SkyWalking Agent 的入口类

你使用的是 Java Agent 9.4.0。其入口类是:

org.apache.skywalking.apm.agent.SkyWalkingAgent

核心入口:

public static void premain(
        String agentArgs,
        Instrumentation instrumentation
) throws PluginException

源码注释直接说明:这是基于 Java Agent 机制的主入口,并使用 Byte Buddy 增强插件定义的类。(GitHub)

你不用记住完整包名,但要记住:

-javaagent
    ↓
SkyWalkingAgent
    ↓
premain()

三、premain() 主要干了哪几件事

从 9.4.0 源码看,主流程可以压缩为四步:

1. 加载配置
2. 加载插件
3. 安装 Class Transformer
4. 启动 Agent 内部服务

对应源码骨架:

public static void premain(
        String agentArgs,
        Instrumentation instrumentation
) {
    // 1. 加载配置
    SnifferConfigInitializer.initializeCoreConfig(agentArgs);

    // 2. 加载插件
    PluginFinder pluginFinder =
        new PluginFinder(
            new PluginBootstrap().loadPlugins()
        );

    // 3. 安装字节码转换器
    installClassTransformer(
        instrumentation,
        pluginFinder
    );

    // 4. 启动 Agent 内部服务
    ServiceManager.INSTANCE.boot();
}

这几行就是整个 Agent 启动主干。(GitHub)


四、第一步:加载配置

源码中首先执行:

SnifferConfigInitializer.initializeCoreConfig(agentArgs);

它会整合不同来源的配置,例如:

agent/config/agent.config
JVM -D 参数
环境变量
-javaagent 后携带的参数

你配置的:

-Dskywalking.agent.service_name=order-service

最终会进入 Agent 的配置对象。

因此,UI 中显示:

order-service

不是 Spring Boot 的:

spring:
  application:
    name: order-service

直接决定的,而是 SkyWalking Agent 自己读取到的服务名。

在你的项目中两者恰好设置成一样,方便统一管理。


五、第二步:加载插件

核心源码:

new PluginBootstrap().loadPlugins()

随后封装成:

PluginFinder

这一步的目的不是立即修改所有类,而是先建立一份规则清单:

哪些类需要增强
增强哪些方法
方法进入前做什么
方法结束后做什么
发生异常时做什么

例如可以抽象为:

Tomcat 插件
  → 匹配 Servlet/Tomcat 请求处理类
  → 创建 EntrySpan

JDBC 插件
  → 匹配 PreparedStatement
  → 创建数据库 ExitSpan

Feign 插件
  → 匹配 Feign HTTP 调用
  → 创建远程 ExitSpan
  → 注入 Trace 上下文

Agent 的插件是可拆分的;官方也将部分 JDK 类插件放在可选的 bootstrap-plugins 目录中,需要时再移动到插件目录。(Apache SkyWalking)


六、第三步:安装字节码转换器

真正关键的是:

installClassTransformer(
        instrumentation,
        pluginFinder
);

内部使用 Byte Buddy 构造:

AgentBuilder

然后:

agentBuilder
    .type(pluginFinder.buildMatch())
    .transform(new Transformer(pluginFinder))
    .installOn(instrumentation);

这一段可以拆成三句话:

type(...)       哪些类需要拦截
transform(...)  如何修改这些类
installOn(...)  把转换器安装进 JVM

SkyWalking 9.4.0 源码中还设置了 RETRANSFORMATION,并在类转换时通过 PluginFinder 找到匹配的插件定义。(GitHub)


七、什么是 Instrumentation

你可以把 Instrumentation 理解为:

JVM 给 Java Agent 的一个“类改造接口”。

Agent 可以通过它在类加载时拿到原始字节码:

.class 原始字节码
       ↓
ClassFileTransformer
       ↓
插入 SkyWalking 拦截逻辑
       ↓
增强后的字节码
       ↓
JVM 加载并执行

业务源码:

public OrderInfo queryOrder(Integer orderId) {
    return orderMapper.selectById(orderId);
}

你看到的源码没变,但 JVM 实际运行的类可能已经被增强成类似:

public OrderInfo queryOrder(Integer orderId) {
    Span span = createSpan("queryOrder");

    try {
        OrderInfo result =
            originalQueryOrder(orderId);

        span.markSuccess();
        return result;
    } catch (Throwable e) {
        span.log(e);
        throw e;
    } finally {
        stopSpan(span);
    }
}

这段是帮助理解的伪代码,不是 SkyWalking 原始源码。


八、第四步:启动 Agent 内部服务

字节码增强安装完成后,源码执行:

ServiceManager.INSTANCE.boot();

它负责启动 Agent 自身需要的后台组件,例如:

服务实例注册
心跳上报
Trace 数据缓冲
gRPC 网络发送
JVM 指标采集
配置拉取

同时,SkyWalking 还注册了 JVM 关闭钩子:

Runtime.getRuntime()
       .addShutdownHook(...)

这样应用退出时,可以关闭 Agent 内部服务并释放资源。(GitHub)

因此 Agent 不只是“改了一下字节码”,它本身还运行着一套后台服务。


九、结合你的 order-service 看完整启动过程

你点击 IDEA 的启动按钮后,实际发生:

JVM 启动
  ↓
发现 -javaagent
  ↓
执行 SkyWalkingAgent.premain()
  ↓
读取 order-service 和 127.0.0.1:11800
  ↓
扫描 agent/plugins 中的插件
  ↓
创建 Byte Buddy AgentBuilder
  ↓
安装 Class Transformer
  ↓
启动 Agent 网络与上报服务
  ↓
执行 OrderApplication.main()
  ↓
Spring Boot 开始创建 Tomcat、HikariCP、Feign 等对象
  ↓
相关类加载时被 Agent 匹配并增强
  ↓
应用启动完成

这就解释了为什么必须:

在服务启动之前配置 -javaagent

而不能等服务完全启动后,才简单引入一个 Maven 包。


十、为什么不是所有类都增强

源码中会明确忽略部分包,例如:

net.bytebuddy
org.slf4j
部分 ASM、反射及 Agent 自身类
合成类

这是为了避免:

Agent 增强自己
递归增强
类加载死循环
不必要的性能开销
兼容性问题

9.4.0 的 installClassTransformer() 中可以直接看到这些忽略规则。(GitHub)

所以 SkyWalking 不是:

把 JVM 中所有类都修改一遍

而是:

插件提供匹配规则
→ 只增强命中的类和方法

十一、这一节最核心的认知

你现在应该把 Agent 启动理解成:

-javaagent
不是“引入一个监控 jar”

而是:

在 main() 之前启动一套字节码增强系统

完整主线:

-javaagent
  ↓
premain()
  ↓
读取配置
  ↓
加载插件规则
  ↓
安装 Transformer
  ↓
Byte Buddy 增强目标类
  ↓
启动上报服务
  ↓
业务 main() 正常运行

十二、面试回答模板

面试官问:

SkyWalking 为什么不修改业务源码也能完成链路追踪?

可以这样回答:

SkyWalking Java Agent 基于 Java Instrumentation。应用通过 -javaagent 启动时,JVM 会在业务 main 方法之前调用 Agent 的 premain。Agent 加载各类插件规则,并利用 Byte Buddy 安装 Class Transformer,在 Tomcat、Feign、JDBC 等目标类加载或重新转换时注入拦截逻辑。拦截器在方法进入、退出和异常阶段创建、结束或标记 Span,再由 Agent 后台服务异步上报给 OAP。因此业务源码基本不需要手工埋点。


十三、本节自测

先自己回答这四个问题:

  1. -javaagent 和普通 Maven 依赖的本质区别是什么?

  2. premain() 为什么比 Spring Boot 的 main() 更早执行?

  3. PluginFinderInstrumentation 分别负责什么?

  4. Agent 安装 Transformer 后,业务类什么时候真正被增强?

标准答案压缩为:

PluginFinder:决定增强谁、怎么增强
Instrumentation:把转换器挂进 JVM
类加载/重转换时:真正修改字节码

下一条主线是:

Tomcat 收到 /order/query/1
→ SkyWalking 如何创建第一个 EntrySpan
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐