Java Agent是什么?
Java Agent是Java平台提供的一个强大工具,它可以在运行时修改或增强Java应用程序的行为。是在JDK1.5以后引入的,它能够在不影响正常编译的情况下修改字节码,相当于是在main方法执行之前的拦截器,也叫premain,也就是会先执行premain方法然后再执行main方法。
使用javaagent可以用于执行一个jar包,并且对这个java包有两个要求:
- jar包的MANIFEST.MF文件必须指定Premain-Class项
- Premain-Class指定的那个类必须实现premain()方法
当程序启动的时候,JVM会首先检查-javaagent所指定的jar包内Premain-Class这个类中的premain方法。
Java Agent的使用场景
- 代码注入增强:允许在程序运行时对字节码进行操作,可以实现功能的增强。
- 性能监控调优:可以监控应用程序方法执行时间、调用次数,类加载的一些信息进行性能检测,以及对一些问题的定位分析,比如一些性能监控和诊断工具如Pinpoint、Skywalking、Zipkin、Arthas等。
- 日志记录审计:Java Agent可以在方法执行前后记录方法的调用信息,包括方法名、参数、返回值等,动态记录应用程序的运行日志。
Java Agent的简单使用
1.基于 Instrumentation 接口和premain()方法实现
新建一个maven项目,添加maven-jar-plugin插件依赖,用于打包并生成MANIFREST.MF文件。
<build><finalName>agenttestone</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><!--自动添加META-INF/MANIFEST.MF --><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><!--permain方法所在类的完全限定名--><Premain-Class>com.yifanghub.agent.PremainTest</Premain-Class><Agent-Class>com.yifanghub.agent.PremainTest</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins></build>
其中manifestEntries配置了一些属性
<Premain-Class>
包含premain方法的类(类的全路径名)
<Agent-Class>
包含agentmain方法的类(类的全路径名)
<Can-Redefine-Classes>
是否可以重定义此代理所需的类,默认为false
<Can-Retransform-Classes>
是否可以重新转换此代理类所需的类,默认为false
新建premain方法测试类,其中premain方法,agentArgs可以通过命令行传入
package com.yifanghub.agent;
import java.lang.instrument.Instrumentation;public class PremainTest {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("premain执行---->");System.out.println("Hello agent test,agentArgs="+agentArgs);}
}
新建main方法测试类(新建一个项目)
package com.yifanghub.agent;public class PremainAgent {public static void main(String[] args) {System.out.println("agent测试走起,main方法执行");}
}
执行main方法,打印如下:
agent测试走起,main方法执行
idea配置:编辑启动类->Add VMOptions
-javaagent:D:\javaagent-demo\javaagent-demo-1.0-SNAPSHOT.jar=123运行程序,打印如下:
premain执行---->
Hello agent test,agentArgs=123
agent测试走起,main方法执行
注意事项:
-
agent的方法名必须是premain,否则会报错。
-
agent抛出异常,会导致主程序的启动失败。
-
premain方法只允许以下两种定义方式
1)public static void premain(String agentArgs, Instrumentation inst)2)public static void premain(String agentArgs)JVM 会优先加载带有Instrumentation参数的方法1,加载成功忽略 2,如果1 没有,加载 2 方法。
2.基于 Attach 接口和agentmain()方法实现
JDK 1.6后引入了agentmain模式,同样提供了一个agentmain方法,可以在main方法执行之后运行
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)
1)创建agentmian方法类
import java.lang.instrument.Instrumentation;public class AgentmainTest {public static void agentmain(String agentArgs, Instrumentation inst) {System.out.println("agentmain执行---->");System.out.println("Hello I am agentmain test,agentArgs="+agentArgs);}
}
将上面代码打包,名字为:javaagent-demo2-1.0-SNAPSHOT.jar
2)创建被注入的main方法测试类(新建一个项目),这里主要使用一个for循环打印,让程序处于一直运行状态
public class MainTest {public static void main(String[] args) {System.out.println("开始执行mian");for (int i = 1; i <= 100000; i++) {System.out.println("第 " + i+ " 次循环");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
3)创建attach项目,编写main方法
这里用到了com.sun.tools.attach
包下面的VirtualMachine工具类来实现,该类表示一个Java虚拟机对象,该类的一些接口如下:
list()
:获取当前所有JVM列表attach()
:根据进程id,连接到jvm上detach()
:断开连接loadAgent()
:加载agent
如果idea里面不能导入VirtualMachine
类,导入JDK里面lib下的tool.jar包即可
attach方法如下:
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;public class AgentDemo4Test {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {System.out.println("AgentDemo4Test");List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor vmd : list) {if(vmd.displayName().equals("agent.MainTest")){System.out.println(vmd.displayName());VirtualMachine vm = VirtualMachine.attach(vmd.id());vm.loadAgent("D:\\javaagent-demo2-1.0-SNAPSHOT.jar","123456");vm.detach();}}}
}
4)启动运行MainTest类(第2步打印for循环的程序),然后执行上面AgentDemo4Test类的main方法,输出如下:
可以看到,我们的agentmain方法的代理已经生效,其中attach
方法可以连接到一个正在运行的Java进程当中,之后便可以通过loadAgent
方法将我们的jar包注入到对应的进程当中,然后被注入的进程就好调用jar包里的agentmain
方法。
注意事项:
- agentmain()方法会在加载之时立即执行,如果agentmain执行失败或抛出异常,JVM 会忽略掉错误,不会影响到正在运行的程序。
参考:https://www.cnblogs.com/LittleHann/p/17462796.html