← 返回题目列表

什么是 Java Agent?premain 和 agentmain 有什么区别?

困难 第 25 / 34 题 更新于 2026/07/28
Java AgentInstrumentationpremainagentmain

简化版

Java Agent 是一种「能介入 JVM、在类加载时或运行时修改字节码」的特殊程序——它靠 JDK 提供的 Instrumentation API 拿到「拦截类字节码」的能力,是 APM 监控(Skywalking、Pinpoint)、诊断工具(Arthas)、热部署(JRebel)的核心技术。它有两种挂载方式:① premain(启动时挂载)——JVM 启动时通过 -javaagent:xxx.jar 加载,在 main 方法之前执行 premain 方法,能在类加载前就注册字节码转换器(最常用);② agentmain(运行时挂载)——JVM 已经运行后,通过 Attach API 动态附加到目标 JVM,执行 agentmain 方法(Arthas 就是这样「附加到正在运行的进程」诊断问题)。核心能力:通过 Instrumentation.addTransformer 注册 ClassFileTransformer,在每个类加载时拿到字节码、用字节码增强库(ASM/ByteBuddy)改它,从而无侵入地插入监控/诊断逻辑。

详细版

Java Agent 的核心:拿到 Instrumentation 对象 → 注册 ClassFileTransformer → 每个类加载时拦截并修改字节码。

premain vs agentmain 对比

维度premain(启动时)agentmain(运行时)
挂载时机JVM 启动时JVM 已运行后动态附加
挂载方式-javaagent:agent.jar 参数Attach API 附加到目标进程
执行方法premain(String args, Instrumentation inst)agentmain(String args, Instrumentation inst)
典型场景APM 监控(启动时插桩全部类)诊断工具 Arthas(附加到问题进程)
能否改已加载的类类还没加载,直接转换类已加载,需 retransformClasses 重新转换
// premain:启动时挂载(MANIFEST.MF 里配 Premain-Class)
public class MyAgent {
    public static void premain(String args, Instrumentation inst) {
        inst.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className,
                    Class<?> classBeingRedefined, ProtectionDomain pd, byte[] classfileBuffer) {
                // 这里用 ASM/ByteBuddy 修改 classfileBuffer 字节码
                return modifiedBytecode;
            }
        });
    }
    // agentmain:运行时挂载(MANIFEST.MF 里配 Agent-Class)
    public static void agentmain(String args, Instrumentation inst) {
        // 附加到已运行的 JVM,可 inst.retransformClasses(重新转换已加载的类)
    }
}

⚠️ Java Agent 是「无侵入监控/诊断」的核心——它让工具能「不改应用代码就介入应用」:premain 用于 APM(启动时 -javaagent 加载探针,给所有类插桩,统计调用链/耗时,如 Skywalking);agentmain 用于诊断(应用已经在跑、出问题了,用 Attach API 动态附加进去查问题,如 Arthas 能 attach 到线上进程看方法调用、改字节码热修复)。两者都靠 Instrumentation API 拿到「拦截+修改字节码」的能力,再配合字节码增强库(ASM/ByteBuddy)实现具体的插桩逻辑。

完整版教学

一、Java Agent 是什么:介入 JVM 的探针

Java Agent 是「一种能在类加载时或运行时介入 JVM、修改字节码」的特殊程序:

普通程序:只能操作自己的对象、数据
Java Agent:能"介入 JVM 本身"
  - 拿到 Instrumentation 对象(JVM 给的"改字节码"的权力)
  - 在每个类加载时拦截字节码、修改它
  - 或对已加载的类重新转换字节码

它是"探针"——像医生用的探针伸进身体里检查,
Java Agent 伸进 JVM 里,观察和修改运行中的类

Java Agent 的本质是「JVM 授予的、拦截和修改字节码的权力」——它通过 Instrumentation 对象(JVM 启动时或附加时传给它)拿到这个权力,就能在类加载时改字节码。它是「让外部工具无侵入地介入应用」的技术。所以「Java Agent 是介入 JVM 修改字节码的探针,是 APM/诊断/热部署工具的核心」。理解「Java Agent = JVM 授予的拦截修改字节码的权力、通过 Instrumentation 实现、是无侵入工具的核心」,就理解了它的定位——它是「伸进 JVM 的探针」。

二、Instrumentation:改字节码的权力

Instrumentation 是 Java Agent 的核心 API——它就是「改字节码的权力」的具体体现:

Instrumentation 的核心能力:
  ① addTransformer(ClassFileTransformer)
     → 注册"字节码转换器",每个类加载时都会调它,让你改字节码
  ② retransformClasses(Class...)
     → 对"已经加载的类"重新转换字节码(agentmain 场景用)
  ③ redefineClasses(...):重定义已加载的类(更强,限制更多)
  ④ getAllLoadedClasses():拿到所有已加载的类

ClassFileTransformer.transform(...) 是核心回调:
  参数有 classfileBuffer(类的原始字节码)
  返回值是"修改后的字节码"(不改就返回 null 或原值)
  → 在这里用 ASM/ByteBuddy 改字节码

Instrumentation 是 JVM 提供的「字节码操作接口」——最核心的是 addTransformer 注册转换器(类加载时拦截)和 retransformClasses(重新转换已加载的类)。真正「怎么改字节码」是在 ClassFileTransformer.transform 回调里用字节码增强库(ASM/ByteBuddy)实现的——Instrumentation 负责「什么时候拦截」,字节码库负责「怎么改」。理解「Instrumentation 是改字节码的权力、addTransformer 注册转换器/retransformClasses 重新转换、transform 回调里用字节码库改」,就理解了 Java Agent 的技术核心——Instrumentation 提供时机,字节码库提供修改能力。

三、premain:启动时挂载

premain 是「JVM 启动时」挂载 Agent 的方式,最常用:

premain 的流程:
  1. 打包 Agent jar,MANIFEST.MF 里配 Premain-Class: MyAgent
     (还要配 Can-Retransform-Classes 等属性)
  2. 启动应用:java -javaagent:myagent.jar -jar app.jar
  3. JVM 启动时,先执行 MyAgent.premain(args, inst)
     ★ 这在 main 方法之前执行!
  4. premain 里 inst.addTransformer(...) 注册转换器
  5. 之后每个类加载时,转换器都被调用,改字节码

关键:premain 在 main 之前执行、在类还没加载时就注册转换器
  → 所以能拦截"所有"类的加载(连应用的类都还没加载呢)

premain 的特点是「在 main 之前、类加载之前执行」——所以它能拦截「所有」类的加载(在应用类加载前就注册好转换器)。这是 APM 监控的标准方式:启动时 -javaagent:skywalking-agent.jar,premain 注册转换器,之后应用的每个类加载时都被插桩(统计耗时、记录调用链)。所以「premain 启动时挂载、在 main 前执行、能拦截所有类、是 APM 监控的标准方式」。理解「premain 通过 -javaagent 启动时挂载、在 main 之前执行、能拦截所有类的加载、用于 APM 全量插桩」,就理解了启动时挂载的用法。

四、agentmain:运行时动态附加

agentmain 是「JVM 已经运行后」动态附加 Agent 的方式:

agentmain 的流程:
  1. Agent jar 的 MANIFEST.MF 配 Agent-Class: MyAgent
  2. 用 Attach API 附加到目标 JVM:
     VirtualMachine vm = VirtualMachine.attach(目标进程pid);
     vm.loadAgent("myagent.jar");
  3. 目标 JVM 执行 MyAgent.agentmain(args, inst)
  4. 此时类"已经加载"了,要改它们得用:
     inst.retransformClasses(已加载的类)  ← 重新转换字节码

关键:agentmain 是"事后附加",类已经加载了
  → 要改已加载的类,得用 retransformClasses 重新转换
  → 有限制:不能加/删方法、字段(只能改方法体)

agentmain 的特点是「事后动态附加」——应用已经在运行(可能出问题了),通过 Attach API 附加进去。因为类已经加载,改它们要用 retransformClasses 重新转换(且有限制:不能加删方法/字段,只能改方法体)。这是诊断工具的方式:Arthas 能 attach 到线上正在运行的进程,查看方法调用、监控性能、甚至热修复字节码。所以「agentmain 运行时动态附加(Attach API)、类已加载要 retransform、用于 Arthas 等诊断工具」。理解「agentmain 通过 Attach API 事后附加到运行中的 JVM、类已加载要用 retransformClasses、用于 Arthas 等诊断」,就理解了运行时挂载的用法。

五、premain vs agentmain:核心区别

对比两种挂载方式,把区别讲透:

维度            premain(启动时)      agentmain(运行时附加)
─────────────────────────────────────────────────────────
挂载时机        JVM 启动时             JVM 已运行后
挂载方式        -javaagent 参数        Attach API 附加进程
执行方法        premain()             agentmain()
类是否已加载    否(在 main 之前)     是(已加载)
改类的方式      addTransformer 直接    retransformClasses 重转
拦截范围        所有类                 已加载的类要重转
典型场景        APM 监控(全量插桩)   诊断工具(附加排查)

核心区别是「挂载时机」:premain 在应用启动前挂载(类还没加载,能拦截所有类,适合 APM 全量插桩);agentmain 在应用运行后附加(类已加载,要重新转换,且有限制,适合诊断已运行的进程)。选择:要监控整个应用生命周期 → premain(启动时 -javaagent);应用已在跑要临时诊断 → agentmain(Attach 附加)。理解「premain 启动时挂载能拦截所有类适合 APM、agentmain 运行时附加已加载类要重转适合诊断、核心区别是挂载时机」,就掌握了两者的选择。

六、实际应用:APM、Arthas、热部署

Java Agent 支撑了一批「无侵入」的重要工具:

① APM 监控(Skywalking、Pinpoint、Zipkin agent):premain
   → 启动时插桩,统计每个方法耗时、构建分布式调用链
   → 应用零改动,全面监控

② 诊断工具(Arthas、BTrace):agentmain(Attach)
   → 附加到线上问题进程,看方法入参/返回值、耗时、
     动态改字节码热修复、火焰图
   → 不重启就能排查线上问题

③ 热部署(JRebel、Spring Loaded):
   → 改了代码不用重启,用 Agent 重新加载修改的类

④ 覆盖率工具(JaCoCo):premain
   → 插桩统计每行代码是否被测试覆盖

这些工具都建立在 Java Agent 之上——它们的「魔法」(零改动监控、不重启诊断、热部署、覆盖率统计)都靠 Agent 的字节码拦截能力。Arthas 尤其经典:它用 agentmain + Attach 附加到线上进程,能实时看方法调用、动态改字节码,是排查线上问题的利器。所以「Java Agent 支撑了 APM 监控、Arthas 诊断、JRebel 热部署、JaCoCo 覆盖率等工具」。理解「Java Agent 支撑 APM(premain 插桩)、Arthas(agentmain 附加诊断)、热部署、覆盖率工具」,就理解了它的实际价值——它是「无侵入工具生态」的基石。

记忆钩子:「Java Agent = 介入 JVM 修改字节码的探针,靠 Instrumentation API(addTransformer 类加载时拦截 / retransformClasses 重转已加载类);两种挂载:premain(启动时 -javaagent 加载、在 main 前执行、能拦截所有类、APM 全量插桩用)、agentmain(运行后用 Attach API 附加、类已加载要 retransform、Arthas 诊断用);transform 回调里用 ASM/ByteBuddy 改字节码;支撑 APM/Arthas/JRebel/JaCoCo」

七、常见误区与追问

  • 误区:Java Agent 要修改应用的源代码。 恰恰相反——它的价值是「无侵入」,应用代码零改动,只在启动时加 -javaagent 或运行时 Attach 附加,Agent 就能在字节码层面插入逻辑。
  • 误区:premain 和 main 谁先执行不确定。 premain 一定在 main 之前执行——这是它能拦截「所有类」加载的前提(在应用类加载前就注册好转换器)。
  • 误区:agentmain 能像 premain 一样随便改类。 不能——agentmain 时类已加载,改它们要用 retransformClasses 重新转换,且有限制(不能加/删方法和字段,只能改方法体);premain 时类还没加载,限制小。
  • 误区:Java Agent 自己就能改字节码。 Instrumentation 只提供「拦截字节码」的时机和权力,真正「怎么改字节码」要在 transform 回调里用字节码增强库(ASM/ByteBuddy)实现——Agent 是「时机」,字节码库是「修改能力」。
  • 追问:Arthas 是怎么 attach 到线上进程的? 用 agentmain 方式 + Attach API:VirtualMachine.attach(pid) 附加到目标 JVM,loadAgent 加载 Agent,执行 agentmain,通过 retransformClasses 增强目标类,从而实时观察方法调用、动态改字节码热修复——不用重启进程。
  • 追问:APM 探针用 premain 还是 agentmain? 通常用 premain——启动时 -javaagent 加载,在 main 之前注册转换器,之后应用所有类加载时都被插桩,实现对整个应用生命周期的全量监控(统计耗时、构建调用链)。
  • 追问:ClassFileTransformer.transform 的作用? 它是 Instrumentation 的核心回调——每个类加载时(或 retransform 时)被调用,参数含类的原始字节码(classfileBuffer),在这里用 ASM/ByteBuddy 修改字节码并返回,返回 null 表示不改。

八、加强记忆

Java Agent 是「能介入 JVM、在类加载时或运行时修改字节码」的特殊程序——靠 JDK 的 Instrumentation APIaddTransformer 类加载时拦截字节码、retransformClasses 重新转换已加载的类)拿到「改字节码的权力」,是 APM 监控(Skywalking)、诊断工具(Arthas)、热部署(JRebel)、覆盖率(JaCoCo) 的核心。两种挂载方式:① premain(启动时)——JVM 启动时通过 -javaagent:xxx.jar 加载、在 main 方法之前执行、类还没加载能拦截所有类、是 APM 全量插桩的标准方式;② agentmain(运行时)——JVM 已运行后通过 Attach API 动态附加(VirtualMachine.attach(pid))、类已加载要用 retransformClasses 重新转换(且不能加删方法/字段)、是 Arthas 附加到线上进程诊断的方式。真正「怎么改字节码」在 ClassFileTransformer.transform 回调里用**字节码增强库(ASM/ByteBuddy)**实现——Instrumentation 提供时机、字节码库提供修改能力。核心价值是「无侵入」:应用代码零改动就能被监控/诊断。一句话「Java Agent = 介入 JVM 改字节码的探针,premain 启动挂载(main 前、拦截所有类、APM 用)vs agentmain 运行时 Attach 附加(类已加载要 retransform、Arthas 用),靠 Instrumentation 定时机 + ASM/ByteBuddy 改字节码」。