Java Agent入门

介绍

在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)。

使用

主程序运行之前的代理程序

创建代理类
public class MyPreMainAgent {

//方法名和参数都是固定的 premain表示在主程序运行之前运行
  public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("PreMain start");
    System.out.println(agentArgs);
    System.out.println(inst);
  }
}

Instrumentation是java1.5新提供的类,它提供在运行时重新加载某个类的的class文件的api。

public interface Instrumentation {
/**
     * 添加一个转换器Transformer,之后的所有的类加载都会被Transformer拦截。
     * ClassFileTransformer类是一个接口,使用时需要实现它,该类只有一个方法,该方法传递类的信息,返回值是转换后的类的字节码文件。
     */
 	void addTransformer(ClassFileTransformer transformer, boolean canRetransform);    

 /**
     * 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
     * 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名
     */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    
/**
   *此方法用于替换类的定义,而不引用现有的类文件字节,就像从源代码重新编译以进行修复和继续调试时所做的那样。
   *在要转换现有类文件字节的地方(例如在字节码插装中),应该使用retransformClasses。
   *该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名
   */
	void redefineClasses(ClassDefinition... definitions)throws  ClassNotFoundException, UnmodifiableClassException;

    /**
     * 获取一个对象的大小
     */
    long getObjectSize(Object objectToSize);
    
    /**
     * 将一个jar加入到bootstrap classloader的 classpath里
     */
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
    
    /**
     * 获取当前被JVM加载的所有类对象
     */
    Class[] getAllLoadedClasses();
}
maven打包
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
			//指明提供代理功能的类
                            <Premain-Class>com.imooc.myagent.MyPreMainAgent</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
客户端调用

在另一个项目中使用这个jar包。

public class Client {
  public static void main(String[] args) {
    System.out.println("main");
  }
}

添加虚拟机启动参数
-javaagent:jar包路径=hah
输出结果为

PreMain start
hah
sun.instrument.InstrumentationImpl@27c170f0
main

结果符合预期,在main方法执行前执行了premain.

主程序运行之后的代理程序

创建代理类
public class MyAgentMainAgent {

//表示在main方法执行之后执行
  public static void agentmain(String agentArgs, Instrumentation inst) {
    System.out.println("AgentMain start");
    System.out.println(agentArgs);
    System.out.println(inst);
//获取所有已加载的类
    Class[] allLoadedClasses = inst.getAllLoadedClasses();
    for (Class loadedClass : allLoadedClasses) {
      System.out.println(loadedClass);
    }
  }
}
maven打包
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
			//指明提供代理功能的类
                            <Agent-Class>com.imooc.myagent.MyAgentMainAgent</Agent-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
客户端调用

在另一个项目中使用这个jar包。在主程序运行之后加载,我们没办法在主程序中调用,只能使用辅助程序然后和主程序通信,这里要用到attach机制。
Attach API是Sun公司提供的一套扩展API,用来向目标JVM"附着"(Attach)代理工具程序的。有了它,开发者可以方便的监控一个JVM,运行一个外加的代理程序,Sun JVM Attach API功能上非常简单,仅提供了如下几个功能:

  1. 列出当前所有的JVM实例描述
  2. Attach到其中一个JVM上,建立通信管道
  3. 让目标JVM加载Agent

jdk提供的jstack,jps功能就是使用该机制实现的。
主程序为

public class Client {
  public static void main(String[] args) {
    while (true) {
      System.out.println("now:" + new Date());
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

让主程序一直在跑。
辅助程序为

public class Client2 {
  public static void main(String[] args)
      throws IOException,
      AttachNotSupportedException,
      AgentLoadException,
      AgentInitializationException {
//代理程序的jar包位置
    String agentPath = "D:\\java\\code_resp\\IdeaProjects\\myagent\\target\\myagent-1.0-SNAPSHOT.jar";
//获取所有实例
    List<VirtualMachineDescriptor> descriptorList = VirtualMachine.list();
    for (VirtualMachineDescriptor descriptor : descriptorList) {
//判断如果是主程序,就加载代理程序
      if (descriptor.displayName().equals(Client.class.getName())) {
        VirtualMachine virtualMachine = VirtualMachine.attach(descriptor);
        virtualMachine.loadAgent(agentPath, "hello");
      }
    }
  }
}

输出结果为

now:Sun Jul 12 15:51:55 CST 2020
now:Sun Jul 12 15:52:05 CST 2020
now:Sun Jul 12 15:52:15 CST 2020
AgentMain start
hello
sun.instrument.InstrumentationImpl@5e84c484
class com.imooc.myagent.MyAgentMainAgent
class com.imooc.sourcecode.java.javaagent.test2.Client
class com.intellij.rt.execution.application.AppMainV2$1
class com.intellij.rt.execution.application.AppMainV2
class com.intellij.rt.execution.application.AppMainV2$Agent
class sun.nio.cs.Surrogate
class sun.nio.cs.Surrogate$Parser
class sun.nio.cs.ISO_8859_1$Encoder
...
...
...

结果符合预期,打印了所有已加载的类。

使用场景

  • apm:(Application Performance Management)应用性能管理。pinpoint、cat、skywalking等都基于Instrumentation实现
  • idea的HotSwap、Jrebel等热部署工具
  • 应用级故障演练
  • Java诊断工具Arthas、Btrace等
posted @ 2020-07-12 16:34  strongmore  阅读(728)  评论(0编辑  收藏  举报