Java Agent 是一个在 Java 虚拟机启动时就注入的 Java 类,可以对 JVM 接口及类库进行访问和修改,常用作 JVM 监控,代码植入等动态工具的实现。以下是 Java Agent 的使用及实现代码攻略。
简介
Java Agent 是 JVM 提供的一种扩展机制,可以在程序运行时动态地增强、修改、监控程序的功能。Java Agent 简单来说就是一种特殊的 Java 程序,可以与应用程序一起运行,并在运行期间修改或测量应用程序的行为和性能。
Java Agent 的主要功能:
- 字节码注入
- 类修改
- JVM 监控及管理
JVM 的启动参数中,可以通过“-javaagent”命令加载 Java Agent 程序。Java Agent 应用场景非常广泛,例如:
- 动态代码注入
- 测试工具
- 性能分析工具
- 安全扫描
- 代码热替换
- 全局日志监控
- 接口分析
实现
下面是一个简单的 Java Agent 的实现方法,实现功能是在每个 Class 加载之前,控制台打印出类名和类加载器信息。
第一步:编写 Java Agent 主类
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new MyTransformer());
}
}
MyAgent 类中包含了 premain 方法,该方法是 Java Agent 执行的入口。premain 方法的参数是设置的字符串参数和 Instrumentation 对象。其中,Instrumentation 对象是一个用于检测和修改 JVM 运行器的 API。
第二步:定义 Class 文件转换器
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class MyTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class<?> clazz, ProtectionDomain domain, byte[] bytes) {
System.out.println("load class: " + className + ", loader: " +
loader.getClass().getName());
return bytes;
}
}
MyTransformer 类是一个 ClassFileTransformer,它实现了 JVM 内部的 ClassFileTransformer 接口。在实现 MyTransformer 类时,最重要的是实现 transform() 方法,该方法将被 Instrumentation API 回调来检测和修改类。
第三步:生成 MANIFEST.MF 文件
Manifest-Version: 1.0
Premain-Class: MyAgent
Can-Redefine-Classes: true
在项目的根目录下,新建文件,命名为 MANIFEST.MF,文件内容包含 Premain-Class 属性和可重定义的类属性。指定 Premain-Class 属性后,JVM 在执行时就会自动定位到该 Class 的 premian 方法。而 Can-Redefine-Classes 属性则是为了让 Java Agent 能够重新定义界面类。
第四步:打包和测试
在项目根目录下,执行以下命令:
$ javac MyAgent.java MyTransformer.java
$ jar -cvfm MyAgent.jar MANIFEST.MF MyAgent.class MyTransformer.class
将编译好的 class 文件和 MANIFEST.MF 文件打成 jar 包。然后执行以下命令:
$ java -javaagent:MyAgent.jar -cp . App
其中,-javaagent:MyAgent.jar 参数指定了 Java Agent 的 jar 包。运行过程将启动 App 类,并对所有加载的类进行输出。
示例
以配置日志和监控 SpringBoot 为例,可以自定义 Java Agent 控制台打印出程序加载到的所有类并分析是否有不必要的类,如下:
import java.lang.instrument.Instrumentation;
public class LoadAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("LoadAgent.premain() was called with options: " + agentArgs);
// 传递 inst 给 MonitorThread
Thread thread = new Thread(new MonitorThread(inst));
thread.setDaemon(true);
thread.start();
}
}
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class ClassLoadTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class<?> clazz, ProtectionDomain domain, byte[] bytes) {
System.out.println("Load Class: " + className + ",Loader: " + loader.getClass().getName());
return bytes;
}
}
import java.lang.instrument.Instrumentation;
public class MonitorThread implements Runnable {
private final Instrumentation inst;
public MonitorThread(Instrumentation inst) {
this.inst = inst;
}
@Override
public void run() {
ClassLoadTransformer clt = new ClassLoadTransformer();
inst.addTransformer(clt);
}
}
在 MANIFEST.MF 加入下面的属性:
Can-Redefine-Classes: true
然后使用 Maven,增加依赖项 jcommander 和 spring-boot-starter。
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.72</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.5</version>
</dependency>
重新生成 jar 文件,运行时添加下面的 JVM 启动参数:
-javaagent:load-agent.jar
-Dlogback.configurationFile=logback-dev.xml
-Djava.security.egd=file:/dev/./urandom
这样就可以在控制台输出 SpringBoot 程序加载的所有 class 信息并分析不必要的类。
结论
使用 Java Agent 可以方便地修改、测量、监控应用程序的行为和性能,达到动态增强的目的。可以将自己的代码注入到任何 Java 应用程序中,例如服务器、GUI 程序和 Applet 这些环境中。希望本文能对读者了解 Java Agent 以及实现代码提供帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java agent 使用及实现代码 - Python技术站