Java Agent 简介

1、写在前面

Java Agent 这个技术出如今 JDK1.5 以后,对于大多数人来讲都比较陌生,可是多多少少又接触过,实际上,咱们平时用的不少工具,都是基于 Java Agent 实现的,例如常见的热部署 JRebel,各类线上诊断工具(Btrace, Greys),还有阿里开源的 Arthas。java

其实 Java Agent 一点都不神秘,也是一个 Jar 包,只是启动方式和普通 Jar 包有所不一样,对于普通的Jar包,经过指定类的 main 函数进行启动,可是 Java Agent 并不能单独启动,必须依附在一个 Java 应用程序运行。apache

咱们可使用 Agent 技术构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其余 JVM 上的程序,使用它能够实现虚拟机级别的 AOP 功能。api

2、动手写一个 Java Agent

首先,咱们先来写一段简单的 Agent 程序:socket

public class AgentTest {
    /**
     * 以 vm 参数的方式载入,在 java 程序的 main 方法执行以前执行
     *
     * @param agentArgs
     * @param inst      Agent技术主要使用的 api,咱们可使用它来改变和从新定义类的行为
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain start");

        System.out.println(agentArgs);
    }

    /**
     * 以 Attach 的方式载入,在 Java 程序启动后执行
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("agentmain start");

        System.out.println(agentArgs);
    }
}

由于 Java Agent 的特殊性,须要一些特殊的配置,例如指定 Agent 的启动类等。这样才能在加载 Java Agent 以后,找到并运行对应的 agentmain 或者 premain 方法。配置方式主要有两种,一种是利用 maven-assembly-plugin 插件(推荐),一种是 MANIFEST.MF 文件。maven

2.1 maven-assembly-plugin 插件

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestEntries>
                            <Premain-Class>org.agent.AgentTest</Premain-Class>
                            <Agent-Class>org.agent.AgentTest</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

2.2 MANIFEST.MF 文件

在 META-INF 目录下建立 MANIFEST.MF 文件:函数

Manifest-Version: 1.0
Agent-Class: org.agent.AgentTest
Premain-Class: org.agent.AgentTest
Can-Redefine-Classes: true
Can-Retransform-Classes: true

值得一提的是,即便新建了 MANIFEST.MF 文件,仍然须要配置 maven-assembly-plugin 信息,不然 MANIFEST.MF 信息会被 Maven 生成的信息覆盖掉。工具

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestFile>
                            src/main/resources/META-INF/MANIFEST.MF
                        </manifestFile>
                    </archive>
                </configuration>
            </plugin>

配置完上面的内容,运行 <b>mvn assembly:single</b> 打包属于 Java Agent 的 jar 包。spa

3、运行你的 Agent 程序

Java Agent 程序写好了,怎么运行它呢?上面看到 Agent 程序分为两种,一种是 premain 函数,在主程序运行以前执行;一种是 agentmain 函数,在主程序运行以后执行。Java 加载这两种 Agent 程序也有区别:插件

3.1 主程序运行前加载

经过 JVM 参数 <b>-javaagent:**.jar[=test]</b> 启动,其中 test 为传入 premain 的 agentArgs 的参数,程序启动的时候,会优先加载 Java Agent,并执行其 premain 方法,这个时候,其实大部分的类都尚未被加载,这个时候能够实现对新加载的类进行字节码修改,可是若是 premain 方法执行失败或抛出异常,那么 JVM 会被停止,这是很致命的问题。命令行

3.2 主程序运行后加载

程序启动以后,经过某种特定的手段加载 Java Agent,这个特定的手段就是 VirtualMachine 的 attach api,这个 api 实际上是 JVM 进程之间的的沟通桥梁,底层经过socket 进行通讯,JVM A 能够发送一些指令给JVM B,B 收到指令以后,能够执行对应的逻辑,好比在命令行中常常使用的 jstack、jps 等,不少都是基于这种机制实现的。

VirtualMachine 的实现位于 tools.jar 中。

<dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

由于是进程间通讯,因此使用 attach api 的也是一个独立的Java进程,下面是一个简单的实现:

public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        VirtualMachine virtualMachine = null;
        try {
            // 1100 是进程号
            virtualMachine = VirtualMachine.attach("1100");
            // 第一个参数是 agent jar包路径,第二个参数为传入 agentmain 的 args 参数
            virtualMachine.loadAgent("D:\\concurrency-0.0.1-SNAPSHOT-jar-with-dependencies.jar", "test");
        } finally {
            if (virtualMachine != null) {
                virtualMachine.detach();
            }
        }

    }

<br> <br> 推荐阅读:

基于 Java Instrument的 Agent 实现

Instrumentation 新功能

相关文章
相关标签/搜索