java agent 入门

Java agent 技术

Java agent的定义

Java agent是一种特殊的Java程序,它在JVM启动时加载,并且能在类加载之前修改类字节码,从而实现各种骚操作:

例如:

  • AOP
  • 监控和性能分析
  • 代码热更新
  • 安全增强

核心原理

从JDK1.5开始,提供了一个java.lang.instrument包,这个包是实现agent的核心API,它包含了 Instrumentation 接口和相关的类。
而Instrumentation是 Java Agent 的核心接口,它提供了很多方法,允许 Agent 操作类。 其中最重要的是 addTransformer() 方法,
它允许 Agent 注册一个 ClassFileTransformer 来修改类的字节码。
ClassFileTransformer 接口,这是一个函数式接口,定义了 transform() 方法。
这个方法接收原始的字节码,并返回修改后的字节码。 Java Agent 可以通过实现这个接口,定义字节码修改逻辑。

premain 方法: 这是 Agent 的入口点,JVM 在启动时会先加载 Agent 的 JAR 文件,然后调用 premain 方法。 premain 方法必须是
public static 的,并且接受两个参数: String agentArgs 和 Instrumentation inst。

agentmain 方法: 这是 Agent 的另一个入口点,JVM 启动后,可以通过 VirtualMachine.loadAgent() 方法动态加载 Agent 的 JAR 文件,
此时会调用agentmain 方法。agentmain 方法必须是 public static 的,并且接受两个参数: String agentArgs 和
Instrumentation inst。

premain和agentmain 是不同启动方式的入口

premain 方法

premain的加载是在main方法之前执行,需要在启动程序时在JVM传递-javaagent参数指定Agent Jar,来触发对应的premain方法,JDK1.5 提供

agentmain 方法

agentmain方法是在JVM启动之后,通过VirtualMachine.loadAgent() 方法动态加载 Agent JAR 包时被调用,JDK1.6开始支持

Java Agent 的优势

  • 非侵入性: 无需修改应用程序源代码,可以在不影响应用程序的情况下添加或修改功能。
  • 动态修改: 可以在运行时修改字节码,实现热更新和动态调整。
  • 全局性: 可以对 JVM 中所有加载的类进行操作,实现全局的监控和控制。
  • 强大的扩展性: 可以通过自定义 ClassFileTransformer 实现各种各样的字节码操作。

起步

现在我们创建一个项目,开始利用ByteBuddy编写一个agent程序

首先需要添加ByteBuddy的依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>最新版本</version>
</dependency>

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>最新版本</version>
</dependency>

作者此时最新版本为1.15.11,最新版本可从GitHub获取点我直达

另外需要注意,在打包时需要生成MANIFEST.MF文件,需要指定一些特殊属性

  • Premain-Class: 指定包含 premain 方法的 Agent 入口类。
  • Agent-Class: 指定包含 agentmain 方法的 Agent 入口类(可选)。
  • Can-Redefine-Classes: 声明 Agent 是否可以重新定义类。
  • Can-Retransform-Classes: 声明 Agent 是否可以重新转换类。
  • (可选)Boot-Class-Path: 当 Agent 需要依赖 JVM 加载路径中没有的库时,需要指定 Boot-Class-Path
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.4.2</version>
    <configuration>
    <archive>
    <manifest>
    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
    </manifest>
    <manifestEntries>
    <Premain-Class>com.mazepeng.AgentMain</Premain-Class>
    <Agent-Class>com.mazepeng.AgentMain</Agent-Class>
    <Main-Class>com.mazepeng.AgentMain</Main-Class>
    <Can-Redefine-Classes>true</Can-Redefine-Classes>
    <Can-Retransform-Classes>true</Can-Retransform-Classes>
    </manifestEntries>
    </archive>
    </configuration>
    </plugin>
    接下来就是编写Agent入口类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.mazepeng;

    import com.mazepeng.utils.AgentRunningUtilities;
    import net.bytebuddy.agent.ByteBuddyAgent;
    import org.apache.commons.cli.*;

    import java.io.File;
    import java.lang.instrument.Instrumentation;
    i

    public class AgentMain {

    public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("premain agent start");
    System.out.println("premain agent end");
    }

    public static void agentmain(String agentArgs, Instrumentation inst){
    System.out.println("agentmain agent start");
    System.out.println("agentmain agent end");
    }

    public static void main(String[] args) {
    CommandLine commandLine = AgentRunningUtilities.parseArgs(args);
    File agentJar = AgentRunningUtilities.getAgentJar();
    System.out.println("agentJar: " + agentJar.getAbsolutePath());
    if (agentJar != null) {
    ByteBuddyAgent.attach(agentJar,commandLine.getOptionValue("pid"));
    }
    }
    }

我们现在就可以通过-javaagent或者attach方式进行调用相应的方法了,关于代码的处理,我会在后续文章给出


java agent 入门
https://mazepeng.com/2025/01/15/java-agent-starter/
作者
马泽朋
发布于
2025年1月15日
许可协议