Java Agent(java探针)虽说在jdk1.5之后就有了,但是对于绝大多数的业务开发javaer来说,这个东西还是比较神奇和陌生的;虽说在实际的业务开发中,很少会涉及到agent开发,但是每个java开发都用过,比如使用idea写了个HelloWorld.java,并运行一下, 仔细看控制台输出
本篇将作为Java Agent的入门篇,手把手教你开发一个统计方法耗时的Java Agent
I. Java Agent开发
首先明确我们的开发环境,选择IDEA作为编辑器,maven进行包管理
1. 核心逻辑
创建一个新的项目(or子module),然后我们新建一个SimpleAgent类
1 | public class SimpleAgent { |
我们先忽略上面两个方法的具体玩法,先简单看一下这两个方法的区别,注释上也说了
- jvm参数形式: 调用 premain 方法
- attach方式: 调用 agentmain 方法
其中jvm方式,也就是说要使用这个agent的目标应用,在启动的时候,需要指定jvm参数 -javaagent:xxx.jar
,当我们提供的agent属于基础必备服务时,可以用这种方式
当目标应用程序启动之后,并没有添加-javaagent
加载我们的agent,依然希望目标程序使用我们的agent,这时候就可以使用attach方式来使用(后面会介绍具体的使用姿势),自然而然的会想到如果我们的agent用来debug定位问题,就可以用这种方式
2. 打包
上面一个简单SimpleAgent就把我们的Agent的核心功能写完了(就是这么简单),接下来需要打一个Jar包
通过maven插件,可以比较简单的输出一个合规的java agent包,有两种常见的使用姿势
a. pom指定配置
在pom.xml文件中,添加如下配置,请注意一下manifestEntries
标签内的参数
1 | <build> |
然后通过 mvn assembly:assembly
命令打包,在target
目录下,可以看到一个后缀为jar-with-dependencies
的jar包,就是我们的目标
b. MANIFEST.MF 配置文件
通过配置文件MANIFEST.MF
,可能更加常见,这里也简单介绍下使用姿势
- 在资源目录(Resources)下,新建目录
META-INF
- 在
META-INF
目录下,新建文件MANIFEST.MF
文件内容如下
1 | Manifest-Version: 1.0 |
请注意,最后的一个空行(如果我上面没有显示的话,多半是markdown渲染有问题),不能少,在idea中,删除最后一行时,会有错误提醒
然后我们的pom.xml
配置,需要作出对应的修改
1 | <build> |
同样通过mvn assembly:assembly
命令打包
II. Agent使用
agent有了,接下来就是需要测试一下使用agent的使用了,上面提出了两种方式,我们下面分别进行说明
1. jvm参数
首先新建一个demo项目,写一个简单的测试类
1 | public class BaseMain { |
测试类中,有一个死循环,各1s调用一下print方法,IDEA测试时,可以直接在配置类,添加jvm参数,如下
请注意上面红框的内容为上一节打包的agent绝对地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
执行main方法之后,会看到控制台输出
请注意上面的premain
, 这个就是我们上面的SimpleAgent
中的premain
方法输出,且只输出了一次
2. attach方式
在使用attach方式时,可以简单的理解为要将我们的agent注入到目标的应用程序中,所以我们需要自己起一个程序来完成这件事情
1 | public class AttachMain { |
上面的逻辑比较简单,首先通过jps -l
获取目标应用的进程号
当上面的main方法执行完毕之后,控制台会输出类似下面的两行日志,可以简单的理解为我连上目标应用,并丢了一个agent,然后挥一挥衣袖不带走任何云彩的离开了
1 | Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket' |
接下来再看一下上面的BaseMain的输出,中间夹着一行agentmain
, 就表明agent被成功注入进去了
3. 小结
本文介绍了maven + idea环境下,手把手教你开发一个hello world版JavaAgent 并打包的全过程
两个方法
方法 | 说明 | 使用姿势 |
---|---|---|
premain() |
agent以jvm方式加载时调用,即目标应用在启动时,指定了agent | -javaagent:xxx.jar |
agentmain() |
agent以attach方式运行时调用,目标应用程序正常工作时使用 | VirtualMachine.attach(pid) 来指定目标进程号 vm.loadAgent("...jar") 加载agent |
两种打包姿势
打包为可用的java agent时,需要注意配置参数,上面提供了两种方式,一个是直接在pom.xml
中指定配置
1 | <manifestEntries> |
另外一个是在配置文件 META-INF/MANIFEST.MF
中写好(需要注意最后一个空行不可或缺)
1 | Manifest-Version: 1.0 |
当然本篇内容看完之后,会发现对java agent的实际开发还是不太清楚,难道agent就是在前面输出一行hello world
就完事了么,这和想象中的完全不一样啊
下一篇博文将手把手教你实现一个方法统计耗时的java agent包,将详细说明利用接口Instrumentation
来实现字节码修改,从而是实现功能增强
II. 其他
0. 源码
1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 扫描关注
一灰灰blog