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功能上非常简单,仅提供了如下几个功能:
- 列出当前所有的JVM实例描述
- Attach到其中一个JVM上,建立通信管道
- 让目标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等