Java Agent(java 探针)虽然说在 jdk1.5 以后就有了,可是对于绝大多数的业务开发 javaer 来讲,这个东西仍是比较神奇和陌生的;虽然说在实际的业务开发中,不多会涉及到 agent 开发,可是每一个 java 开发都用过,好比使用 idea 写了个 HelloWorld.java,并运行一下, 仔细看控制台输出git
本篇将做为 Java Agent 的入门篇,手把手教你开发一个统计方法耗时的 Java Agentgithub
<!-- more -->apache
首先明确咱们的开发环境,选择 IDEA 做为编辑器,maven 进行包管理markdown
建立一个新的项目(or 子 module),而后咱们新建一个 SimpleAgent 类jvm
public class SimpleAgent { /** * jvm 参数形式启动,运行此方法 * * @param agentArgs * @param inst */ public static void premain(String agentArgs, Instrumentation inst) { System.out.println("premain"); } /** * 动态 attach 方式启动,运行此方法 * * @param agentArgs * @param inst */ public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("agentmain"); } }
咱们先忽略上面两个方法的具体玩法,先简单看一下这两个方法的区别,注释上也说了socket
其中 jvm 方式,也就是说要使用这个 agent 的目标应用,在启动的时候,须要指定 jvm 参数 -javaagent:xxx.jar
,当咱们提供的 agent 属于基础必备服务时,能够用这种方式maven
当目标应用程序启动以后,并无添加-javaagent
加载咱们的 agent,依然但愿目标程序使用咱们的 agent,这时候就可使用 attach 方式来使用(后面会介绍具体的使用姿式),天然而然的会想到若是咱们的 agent 用来 debug 定位问题,就能够用这种方式编辑器
上面一个简单 SimpleAgent 就把咱们的 Agent 的核心功能写完了(就是这么简单),接下来须要打一个 Jar 包ide
经过 maven 插件,能够比较简单的输出一个合规的 java agent 包,有两种常见的使用姿式
在 pom.xml 文件中,添加以下配置,请注意一下manifestEntries
标签内的参数
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class> <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <goals> <goal>attached</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
而后经过 mvn assembly:assembly
命令打包,在target
目录下,能够看到一个后缀为jar-with-dependencies
的 jar 包,就是咱们的目标
经过配置文件MANIFEST.MF
,可能更加常见,这里也简单介绍下使用姿式
META-INF
META-INF
目录下,新建文件MANIFEST.MF
文件内容以下
Manifest-Version: 1.0 Premain-Class: com.git.hui.agent.SimpleAgent Agent-Class: com.git.hui.agent.SimpleAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
请注意,最后的一个空行(若是我上面没有显示的话,多半是 markdown 渲染有问题),不能少,在 idea 中,删除最后一行时,会有错误提醒
而后咱们的pom.xml
配置,须要做出对应的修改
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile> <!--<manifestEntries>--> <!--<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>--> <!--<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>--> <!--<Can-Redefine-Classes>true</Can-Redefine-Classes>--> <!--<Can-Retransform-Classes>true</Can-Retransform-Classes>--> <!--</manifestEntries>--> </archive> </configuration> <executions> <execution> <goals> <goal>attached</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
一样经过mvn assembly:assembly
命令打包
agent 有了,接下来就是须要测试一下使用 agent 的使用了,上面提出了两种方式,咱们下面分别进行说明
首先新建一个 demo 项目,写一个简单的测试类
public class BaseMain { public int print(int i) { System.out.println("i: " + i); return i + 2; } public void run() { int i = 1; while (true) { i = print(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { BaseMain main = new BaseMain(); main.run(); Thread.sleep(1000 * 60 * 60); } }
测试类中,有一个死循环,各 1s 调用一下 print 方法,IDEA 测试时,能够直接在配置类,添加 jvm 参数,以下
请注意上面红框的内容为上一节打包的 agent 绝对地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
执行 main 方法以后,会看到控制台输出
请注意上面的premain
, 这个就是咱们上面的SimpleAgent
中的premain
方法输出,且只输出了一次
在使用 attach 方式时,能够简单的理解为要将咱们的 agent 注入到目标的应用程序中,因此咱们须要本身起一个程序来完成这件事情
public class AttachMain { public static void main(String[] args) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException { // attach方法参数为目标应用程序的进程号 VirtualMachine vm = VirtualMachine.attach("36633"); // 请用你本身的agent绝对地址,替换这个 vm.loadAgent("/Users/......./target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar"); } }
上面的逻辑比较简单,首先经过jps -l
获取目标应用的进程号
当上面的 main 方法执行完毕以后,控制台会输出相似下面的两行日志,能够简单的理解为我连上目标应用,并丢了一个 agent,而后挥一挥衣袖不带走任何云彩的离开了
Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket' Disconnected from the target VM, address: '127.0.0.1:63710', transport: 'socket'
接下来再看一下上面的 BaseMain 的输出,中间夹着一行agentmain
, 就代表 agent 被成功注入进去了
本文介绍了 maven + idea 环境下,手把手教你开发一个 hello world 版 JavaAgent 并打包的全过程
两个方法
方法 | 说明 | 使用姿式 |
---|---|---|
premain() |
agent 以 jvm 方式加载时调用,即目标应用在启动时,指定了 agent | -javaagent:xxx.jar |
agentmain() |
agent 以 attach 方式运行时调用,目标应用程序正常工做时使用 | VirtualMachine.attach(pid) 来指定目标进程号 <br/> vm.loadAgent("...jar") 加载 agent |
两种打包姿式
打包为可用的 java agent 时,须要注意配置参数,上面提供了两种方式,一个是直接在pom.xml
中指定配置
<manifestEntries> <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class> <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries>
另一个是在配置文件 META-INF/MANIFEST.MF
中写好(须要注意最后一个空行不可或缺)
Manifest-Version: 1.0 Premain-Class: com.git.hui.agent.SimpleAgent Agent-Class: com.git.hui.agent.SimpleAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
固然本篇内容看完以后,会发现对 java agent 的实际开发仍是不太清楚,难道 agent 就是在前面输出一行hello world
就完事了么,这和想象中的彻底不同啊
下一篇博文将手把手教你实现一个方法统计耗时的 java agent 包,将详细说明利用接口Instrumentation
来实现字节码修改,从而是实现功能加强
一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛
尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
一灰灰 blog