java的agent探针技术
介绍
Java Agent
直译过来就是java
代理或者有的地方叫做java
探针。这个jar
包不能独立运行,需要依附于目标JVM
进程中。主要作用是通过对JVM
进程进行代理,可以在目标JVM
运行过程中获取到对应虚拟机中相关参数。
主要应用场景为:热部署、IDEA
的DEBUG
调试功能,Arthas
线上诊断工具、性能监控等。
使用
java Agent
有两个入口,两个入口的执行时机有所不同,一个是在目标应用启动之前,一个是在目标应用运行过程中。
目标应用刚启动时:
通过在目标应用的启动参数中添加-javaagent:xxx/xxx/xxxAgent.jar
的方式启动,在执行目标应用的main
方法之前会进入到xxxAgent.jar
的premain()
方法中。
1 2 3 4 5 6 7
| public static void premain(String agentArgs, Instrumentation inst) { System.out.println("------ premain方法,有一个入参 ------ agentArgs:" + agentArgs); } public static void premain(String agentArgs){ System.out.println("------ premain方法,有一个入参 ------ agentArgs:" + agentArgs); }
|
注意:这种方式是在目标应用要执行main
方法,之前进入这个premain
方法,也就是说这时jvm
虚拟机中并没有加载过多的类。由于这种加载逻辑,导致在基本上在类加载之后就无法修改字节码,所以这种方式存的灵活性存在一定的限制。
目标应用运行过程中:
通过中间程序动态附着到目标JVM
中的方式启动,所以中间程序就可以做到动态化,也就是达到某个条件之后就附着。
agent.jar
:
1 2 3 4 5 6 7
| public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("------ agentmain方法 有两个入参 ------ agentArgs:" + agentArgs + " inst:" + inst.toString()); } public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("------ agentmain方法 有两个入参 ------ agentArgs:" + agentArgs); }
|
BridgeProject
(中间程序):
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) throws InterruptedException, AgentLoadException, IOException, AgentInitializationException, AttachNotSupportedException { VirtualMachine vm = null; for(VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()){ if(vmDescriptor.displayName().contains("XxxBootstrap")){ System.out.println("进入附着jvm"); vm = VirtualMachine.attach(vmDescriptor); } } vm.loadAgent("xxx/xxx/agent.jar"); }
|
也就是说拥有XxxBootstrap
启动类的目标应用当前正处在运行状态,然后启动中间程序BridgeProject#main
方法,它会获取XxxBootstrap
对应的虚拟机之后,将agent.jar
附着到该虚拟机上,这时就会进入到agent.jar
包中的agentmain
方法,就可以动态的进行一些处理。
Arthas
就是使用agentmain
这种方式,它在首次使用的时候是仅下载这个中间程序curl -O https://arthas.aliyun.com/arthas-boot.jar
,当通过java -jar arthas-boot.jar
命令启动这个springboot
程序之后,它会罗列当前环境中的虚拟机(如果你开多个应用,会有多个虚拟机)。当你选中某个应用之后,它会校验arthasHome=~/.arthas/lib/
这个目录是否有agent.jar
包,如果没有它会去下载agent.jar
包,当然它的逻辑会更复杂,它不单单只有agent.jar
包。也就是说实际上通过curl
下载的arthas-boot.jar
实际上是对上述中间程序的一个封装,做一些外围非核心逻辑的处理,当选中某个应用时,再去下载核心agent.jar
包,进行探针修改字节码相关逻辑处理。
Instrumentation
相关接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
addTransformer();
redefineClasses();
retransformClasses();
appendToBootstrapClassLoaderSearch();
appendToSystemClassLoaderSearch();
getAllLoadedClasses();
|
注意点
1.打包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <appendAssemblyId>false</appendAssemblyId> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> </manifest> <manifestEntries> <Premain-Class>cn.com.xiaocainiaoya.Agent</Premain-Class> <Agent-Class>cn.com.xiaocainiaoya.Agent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
|
注意这里的descriptorRef
这是会将pom
中的依赖打入到agent.jar
包中,所以是否启动这个要取决于你的探针中需要的类库在目标程序中是否存在。比如我的目标程序中不存在directory-watcher
依赖,但是agent.jar
是需要这个依赖,所以我这里使用descriptorRef
将这个依赖也打入到最终的agent.jar
包中,如果不希望部分依赖打入到agent.jar
包中(如果主工程中也存在这个依赖,会出现冲突的情况),需要在pom.xml
的依赖项中添加<scope>
标签。
1 2 3 4 5 6
| <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> <scope>provided</scope> </dependency>
|
其中VirtualMachine
是JDK
工具包下的类,如果系统环境变量没有配置,需要自己在Maven
中引入本地文件。一般采用方案二时,动态附着需要使用到这些类。
1 2 3 4 5 6 7
| <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/lib/tools.jar</systemPath> </dependency>
|
通过实现这个类ClassFileTransformer
,然后通过Instrumentation
添加到类转换器中,即可对类进行动态修改。
1
| inst.addTransformer(new ProfilingTransformer());
|