IT序号网

Java Agent入门知识解答

flyfish 2022年04月08日 编程语言 122 0
本文章主要介绍了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等

发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

JavaPoet入门知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。