java.lang.instrument 包提供了这样的一种能力:允许开发者使用 Java Agent 来探测 JVM 中正在运行的应用程序。其中,探测或修改应用程序需要通过修改程序的字节码来实现。

一个 Java Agent 程序是以 Jar 包的形式部署,Java Agent 程序打包生成的 Jar 包中的 META-INF/MANIFEST.MF 文件中的属性(Premain-ClassAgent-Class)指定了引导这个 Java Agent 程序的主类。Java Agent 程序可以通过添加新的 JVM 启动参数选项,来以 Command-Line 的方式加载和使用。除此之外,Java Agent 程序也支持在 JVM 启动之后再加载的方式,不过这种使用方式的具体实现是与 JVM 平台独立无关的。

加载 Java Agent

通过 Command-Line

在 Command-Line 使用方式中,一个 Java Agent 程序是通过在 JVM 的启动参数中添加 -javaagent:jarpath[=options] 选项来加载的。其中,jarpath 是 Java Agent 对应 Jar 包的路径,options 是启动 Java Agent 所需的参数。

在这种使用方式中,Java Agent 对应 Jar 包中的 META-INF/MANIFEST.MF 文件需要含有 Premain-Class 属性,Premain-Class 属性值是引导这个 Java Agent 程序启动的主类,并且这个类需要含有 public static void premain(String agentArgs, Instrumentation inst) 或者 public static void premain(String agentArgs) 签名的方法(与一般 Java 应用程序的主类需要含有 public static void main(String[] args) 签名的方法类似)。

Java Agent 程序中的主类会被应用程序的 ClassLoader#getSystemClassLoader() 类加载器所加载,这个类加载器通常也会加载应用程序的主类。如果 Java Agent 程序无法被 JVM 加载或者加载时抛出异常,那么 JVM 的启动流程将会中止。

在 JVM 启动之后加载

在 JVM 启动之后再加载 Java Agent 使用方式中,Java Agent 的初始化细节是基于应用特定实现的,但通常来说,这是发生在应用已经启动和加载主类之后的事情。

在这种使用方式中,Java Agent 对应 Jar 包中的 META-INF/MANIFEST.MF 文件需要含有 Agent-Class 属性,Agent-Class 属性值是引导这个 Java Agent 程序启动的主类,并且这个类需要含有 public static void agentmain(String agentArgs, Instrumentation inst) 或者 public static void agentmain(String agentArgs) 签名的方法。

Java Agent 程序中的主类会被应用程序的 ClassLoader#getSystemClassLoader() 类加载器所加载,并且要求这个类加载器支持将 Java Agent 对应 Jar 包添加到系统类路径的机制。如果 Java Agent 程序无法被 JVM 加载或者加载时抛出异常,JVM 则会忽略异常,并且不会中止退出。

原文链接:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html

使用案例

Java Agent 可以被应用于很多场景,例如;

  • arthas 使用 Java Agent 作为探测应用状态程序的启动类;
  • transmittable-thread-local 支持使用 Java Agent 无侵入地方式装饰 JDK 线程池实现类;

代码示例

Java Agent 的主类:

  1. package com.demo;
  2. import java.lang.instrument.Instrumentation;
  3. public class MyAgent {
  4. public static void premain(String agentArgs, Instrumentation inst) {
  5. // 此处通常会操作字节码,修改应用程序逻辑,实现监控、热部署、AOP 等功能
  6. System.out.println("Hello My Java Agent");
  7. }
  8. }

Java Agent 的 Maven 打包方式:

  1. <groupId>com.demo</groupId>
  2. <artifactId>MyAgent</artifactId>
  3. <version>1.0</version>
  4. ......
  5. <build>
  6. <plugins>
  7. <plugin>
  8. <artifactId>maven-jar-plugin</artifactId>
  9. <version>3.1.2</version>
  10. <configuration>
  11. <archive>
  12. <!-- see https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html -->
  13. <manifestEntries>
  14. <!-- The manifest of the agent JAR file must contain the attribute Premain-Class. -->
  15. <Premain-Class>com.demo.MyAgent</Premain-Class>
  16. <!-- Is the ability to redefine classes needed by this agent. -->
  17. <Can-Redefine-Classes>false</Can-Redefine-Classes>
  18. <!-- Is the ability to retransform classes needed by this agent. -->
  19. <Can-Retransform-Classes>true</Can-Retransform-Classes>
  20. <!-- Is the ability to set native method prefix needed by this agent. -->
  21. <Can-Set-Native-Method-Prefix>false</Can-Set-Native-Method-Prefix>
  22. </manifestEntries>
  23. </archive>
  24. </configuration>
  25. </plugin>
  26. </plugins>
  27. </build>

Java Agent 的 JVM 启动参数:

  1. java -javaagent:.../MyAgent-1.0.jar -jar application.jar