# 6.1 Java Agent 基础

# 6.1.1 实现一个简单的Java Agent

Agent的代码如下:

package org.example;

import java.lang.instrument.Instrumentation;

public class Agent {

    /**
     * 以vm参数的方式载入,在Java程序的main方法执行之前执行
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain run");
    }

    /**
     * 以Attach的方式载入,在Java程序启动后执行
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("agentmain run");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们需要在agentmain或者premain方法中实现具体的Agent逻辑,这里是你大显身手的地方, 如读取JVM的各种数据,修改类的字节码,只要你能想到的,一般都可以实现。

因为Java Agent的特殊性,还需要一些特殊的配置,在META-INF目录下创建MANIFEST.MF文件, 这部分可以手动生成也可以使用maven插件自动生成,这里建议使用maven插件自动生成。 在pom.xml文件中添加如下插件配置,其中Premain-ClassAgent-Class的配置值为上面Agent类的全限定名称。 配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Premain-Class>org.example.Agent</Premain-Class>
                        <Agent-Class>org.example.Agent</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

解压jar包查看META-IN/MANIFEST.MF文件,Java Agent 的入口类org.example.Agent已经被写入到文件中。

Manifest-Version: 1.0
Premain-Class: org.example.Agent
Archiver-Version: Plexus Archiver
Built-By: jrasp
Agent-Class: org.example.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_261


1
2
3
4
5
6
7
8
9
10
11
12

# 6.1.2 加载Agent

前面说了,一个Java Agent既可以在程序运行前加载,也可以在程序运行后加载,两者有什么区别呢?

  • 命令行方式启动
-javaagent:/path/to/your/jarpath[=options] 
1

参数是可选择的,如jacoco的启动参数如下:

java -javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar application.jar
1

premain方法允许有如下2种方法签名:

public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);  
1
2

当以上两种方式都存在时,带有Instrumentation参数的方法的优先级更高,会被JVM优先调用。

这里以命令行形式在SpringBoot应用中加载一个Java Agent。

java -javaagent:/path/to/your/my-agent-1.0.jar -jar springboot.jar
1

可以看到"premain run"日志输出。

  • 运行时加载

应用程序启动之后,通过某种特定的手段加载Java Agent,这个特定的手段就是VirtualMachine的 attach api, 这个api其实是JVM进程之间的的沟通桥梁,底层通过socket进行通信,JVM A可以发送一些指令给JVM B,B收到指令之后, 可以执行对应的逻辑,比如在命令行中经常使用的jstack、jcmd、jps等,很多都是基于这种机制实现的。 在前面的章节实现了attach,这里不再重复。

# 6.1.3 Agent的功能开关

以下是agent jar文件的Manifest Attributes:

  • Premain-Class

如果JVM启动时指定了代理,那么此属性指定代理类,即包含premain方法的类。 如果JVM启动时指定了代理,那么此属性是必需的。 如果该属性不存在,那么JVM将中止。注:此属性是类名,不是文件名或路径。

  • Agent-Class 如果实现支持JVM启动之后某一时刻启动代理的机制,那么此属性指定代理类。

  • 即包含 agentmain 方法的类。 此属性是必需的,如果不存在,代理将无法启动。

  • 注:这是类名,而不是文件名或路径。

  • Boot-Class-Path 由引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常作为 JAR 或 zip 库被引用)。

  • 查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。此属性是可选的。

  • Can-Redefine-Classes 布尔值(true 或 false,与大小写无关)。是否能重定义此代理所需的类。

  • true以外的值均被视为false。此属性是可选的,默认值为false。

  • Can-Retransform-Classes 布尔值(true 或 false,与大小写无关)。是否能重转换此代理所需的类。

  • true以外的值均被视为false。此属性是可选的,默认值为 false。

  • Can-Set-Native-Method-Prefix 布尔值(true 或 false,与大小写无关)。是否能设置此代理所需的本机方法前缀。

  • true 以外的值均被视为 false。此属性是可选的,默认值为 false。允许当前agent给native方法设置前缀,可以间接实现native字节码的修改

一个Agent jar 包中可以同时存在Premain-Class和Agent-Class,当 javaagent以命令行方式启动,仅使用Premain-Class,而忽略Agent-Class, 以运行时启动javaagent,则相反。

上面的6个属性在Java Agent中都有使用。可以参考官方文档:

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/compact3-package-summary.html