Java的APM工具原理
背景
在 JDK 1.5时代,SUN推出了 一个可以动态改变class内容的工具,在 启动之前,增加一个 -javaagent
参数
这个参数后面跟的是一个自定义的jar,jar包里面就是要执行的动态注入逻辑
然后实现自定义的premain
函数(需要在 MANIFEST.MF 文件中指定启动类),就可以动态修改运行期间的 class内容,达到动态替换的效果
到1.6之后,可以根据 进程的PID,动态attach到这个进程上,然后动态修改已经加载的Class内容
这里的核心是 Instrumentation
,它允许修改已经加载的 Class,这样就达到了动态替换的效果
|
|
而 1.6 之后,目标JVM上会启动一个等待注入的socket,等拿到目标JVM的PID后,可以将这个jar动态挂载到目标JVM上,这样就可以实现真正的动态注入了
用途
实现动态注入的几个关键技术点:
- attach socket
- Instrumentation,属于 JPDA(Java platform debugger architecture)体系
- 可以生成字节码的工具,如:ASM等
通过使用JVM提供的Instrumentation API来开发出来的jar包,可以用来修改已加载到JVM中的字节码文件
这个东西能玩出很多花样来,最典型的就是 APM,以及动态 trace 这些功能
方便监控、debug排查问题等
attach
当 使用 jstack pid 时候,由两个线程负责完成这个任务
一个是 Signal Dispatcher 线程,这个线程负责接受一个 信号,也就是 quit 的信号
然后负责初始化 attach 的socket,如果初始化失败就直接打印在控制台上,比如 kill -3 就不会初始化
attach socket并不是 JVM启动后就创建的,是通过 Signal Dispatcher 来创建的
attach socket负责接受 UNIX 的socket
attach可以接受各种指令:
|
|
其中就包含 dump线程、agent 等
JPDA体系
整个 JPDA体系分为三层,由低到高:
- Java 虚拟机工具接口(JVMTI)
- Java 调试协议(JDWP)
- Java 调试接口(JDI)
正式通过这三层结构提供的能力,给我们提供了调试功能。利用JVMTI提供的能力,可以实现对JVM的多种操作,
它通过接口注册各种事件勾子,在JVM事件触发时,同时触发预定义的勾子,以实现对各个JVM事件的响应,
事件包括类文件加载、异常产生与捕获、线程启动和结束、进入和退出临界区、成员变量修改、GC开始和结束、
方法调用进入和退出、临界区竞争与等待、VM启动与退出等等。
例子
将下面这些类,打成jar
包
|
|
打包
MANIFEST.MF文件内容如下
|
|
maven的pom文件中需要增加一个打包插件:
|
|
执行
执行一个测试类:
|
|
启动类,需要将 JDK libs目录下的 tools.jar导入工程
|
|
执行效果
|
|
参考
Instrumentation API
A BEGINNER’S GUIDE TO JAVA AGENTS
Java Instrumentation API开发Java Agent学习记
JVMTI Attach机制与核心源码分析
字节码增强