-javaagent 是 Java 虚拟机(JVM)的启动参数,用于在 Java 应用程序启动时加载一个 Java 代理(Agent)。Java 代理允许你在不修改应用程序代码的情况下动态地拦截和修改字节码,常用于性能监控、调试、日志记录、性能分析以及应用程序的动态修改等场景

1、使用 -javaagent 的场景

  • 性能监控工具:如 APM(Application Performance Monitoring)工具,如 New Relic、Prometheus JMX exporter 等,常通过 -javaagent 参数加载,监控应用的性能数据。
  • 字节码增强:使用工具如 ASM 或 Javassist 对应用程序的字节码进行动态修改。
  • 动态 AOP(面向切面编程):通过代理进行方法拦截,执行前后逻辑(如 Spring AOP 可以通过代理实现动态拦截功能)

    2、实例

    2.1、创建my-agent

    通过 Java Agent 动态修改字节码,记录方法执行时间

pom.xml如下 :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>my-agent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>my-agent</name>
  <url>http://maven.apache.org</url>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.28.0-GA</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <archive>
            <manifestEntries>
              <Premain-Class>com.example.TimingAgent</Premain-Class>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.4</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

TimingAgent.java

package com.example;

import java.lang.instrument.Instrumentation;

public class TimingAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("Timing Agent loaded.");
        System.out.println("Agent arguments: " + agentArgs);
        inst.addTransformer(new TimingTransformer());
    }
}

TimingTransformer.java
TimingTransformer 类将拦截并修改目标类的字节码,记录方法的开始和结束时间

package com.example;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class TimingTransformer implements ClassFileTransformer {


    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        // 我们只对 com/example 包下的类进行修改
        if (!className.startsWith("com/example")) {
            return classfileBuffer; // 不修改其他类的字节码
        }

        try {
            // 使用 Javassist 来修改字节码
            ClassPool classPool = ClassPool.getDefault();
            CtClass ctClass = classPool.get(className.replace('/', '.'));

            for (CtMethod method : ctClass.getDeclaredMethods()) {
                // 在方法的开头记录开始时间
                method.addLocalVariable("startTime", CtClass.longType);
                method.insertBefore("startTime = System.currentTimeMillis();");

                // 在方法的末尾记录结束时间,并打印执行时间
                method.insertAfter("System.out.println(\"Execution duration: \" + (System.currentTimeMillis() - startTime) + \" ms\");");
            }
            return ctClass.toBytecode(); // 返回修改后的字节码
        } catch (Exception e) {
            e.printStackTrace();
        }

        return classfileBuffer; // 如果出错,则返回原始字节码
    }
}

设置 MANIFEST 文件
创建 META-INF/MANIFEST.MF 文件,用于指定代理的入口类

Manifest-Version: 1.0
Premain-Class: com.example.TimingAgent

打包
mvn clean package
路径如下 : /Users/xxx/IdeaProjects/agent/my-agent/target/my-agent-1.0-SNAPSHOT.jar

2.2、创建my-app

使用默认的maven项目即可,因为是默认,所以pom.xml默认即可

创建一个MyApp的类,一定要是com.example这个包下

package com.example;

public class MyApp {
    public static void main(String[] args) throws InterruptedException {
        MyApp app = new MyApp();
        app.doWork();
    }

    public void doWork() throws InterruptedException {
        System.out.println("Doing some work...");
        Thread.sleep(1000); // 模拟耗时操作
        System.out.println("Work completed.");
    }
}

IDEA中如下配置 :
JVM参数如下 : -javaagent:/Users/qiaozhanwei/IdeaProjects/agent/my-agent/target/my-agent-1.0-SNAPSHOT.jar=arg1,arg2,arg3
image.png

执行结果如下 :

Timing Agent loaded.
Agent arguments: arg1,arg2,arg3
Doing some work...
Work completed.
Execution duration: 1005 ms
Execution duration: 1005 ms

journey
32 声望21 粉丝