预备知识
类似JVM中的AOP
Java的agent机制
Instructation机制
使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。
有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,
这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,
使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
-- 百度百科
instrument的底层实现依赖于JVMTI,也就是JVM Tool Interface,它是JVM暴露出来的一些供用户扩展的接口集合。
JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
使用instrument 的方式有两种,下面分别简单总结一下:
premain方式(JVM启动前进行增强)
需要实现下面的方法
public static void premain(String args, Instrumentation instrumentation){
//在这里调用instrumentation 做增强操作
}
并在打包jar的时候,指定Premain-Class 的入口类
可以参考arthas中arthas-agent/pom.xml
运行原理如下图:
在目标java程序启动运行前,通过-javaagent=agent.jar=paramsxxxx 的方式,可以在目标jvm运行前,进行字节码的修改
常见的用法
idea破解:使用agent的方式,替换加密的文件、证书
agentmain方式(JVM运行中进行增强)
原理:使用attach机制,与目标jvm建立链接
使用步骤:
- 1、编写agent实现类
需要实现下面的方法
public static void agentmain(String args, Instrumentation instrumentation){
}
- 2、打包为agent.jar
并在打包jar的时候,指定Agent-Class 的入口类
可以参考arthas中arthas-agent/pom.xml
- 3、编写注入程序,调用attach相关的api
使用时,需要先通过jvm的工具,获取jvm实例,然后调用
com.sun.tools.attach.VirtualMachine.loadAgent(java.lang.String, java.lang.String)
去给目标jvm注入agent.jar
- 4、注入后,目标jvm就会运行上面定义的agentmain 方法,进而通过 Instrumentation 实例对jvm实例进行增强
运行原理如下所示:
常见的应用案例:
arthas
arthas核心模块的功能和流程
arthas的核心模块不多,主要是以下几个:
arthas-common
类如下所示:
主要是存放公共类、工具类
如io相关的、文件相关的、反射相关的工具
arthas-apy
只有一个类
定义此类,用于依附到目标jvm中,并实现目标jvm与arthas的类隔离,后续会深入研究
arthas-client
核心类是 com.taobao.arthas.client.TelnetConsole
对应入口函数是process(java.lang.String[], java.awt.event.ActionListener)
供arthas-boot使用,在boot的进程中起一个客户端线程,用于接收用户的输入、输出arthasc-core的输出内容
1、在com.taobao.arthas.client.IOUtil#readWrite 中会起两个线程,
- 一个reader线程:用于读取用户在本地「终端」的输入,将其输出到远程的输出流中
- 一个writer线程:用于读取远程的输入流,将其输出到本地「终端」的输出流中
上面的 本地「终端」,指的是标准输入输出
上面的 远程,指的是通过attach机制注入到目标jvm后,启动的监听端口所产生的连接
此外,连接远程的时候,会使用 org.apache.commons.net.telnet.TelnetInputStream 这个Runnable 去启动一个线程
arthas-boot
使用 java -jar arthas-boot.jar 的方式启动arthas的时候,启动的就是本模块下的com.taobao.arthas.boot.Bootstrap的main 方法
启动后,会在做一下几件事情
-
1、java版本的判断,做一些准备
- 检查jdk版本的兼容性
- 检查telnet端口是否已被占用
-
2、列出本机上存在的jvm进程和对应的pid,让用户选择要监听的jvm进程
-
3、选中要监听的jvm的pid后,会检查依赖的一下几个jar是否存在
“arthas-core.jar”, “arthas-agent.jar”, “arthas-spy.jar”
若相关依赖不存在,则会去官网上下载
-
4、若依赖存在,且telnet端口没有被占用,则会在此去检查telnet端口是否被占用
- 若存在,则忽略
- 若不存在,则通过 com.taobao.arthas.boot.ProcessUtils#startArthasCore 方法,对目标jvm启动arthas-core
-
使用ProcessBuilder ,构造一个进程,然后使用命令行的方式,调用 com.taobao.arthas.core.Arthas#main
-
继续调用 com.taobao.arthas.core.Arthas#attachAgent
-
最终调用com.sun.tools.attach.VirtualMachine.loadAgent(java.lang.String, java.lang.String)
将agent注入到目标jvm中
-
-
5、通过反射的方式,调用com.taobao.arthas.client.TelnetConsole#main,启动本地「终端」
arthas-core
arthas的核心模块,被attach到目标jvm后,会执行一系列的动作
入口在 com.taobao.arthas.agent334.AgentBootstrap#agentmain
该模块比较复杂,会是后续研究的重点
ps:
也可以在IDEA中全局搜索pom.xml中的关键字「Agent-Class」,来确定attach的入口
参考
- 1、https://www.cnblogs.com/zhenjingcool/p/16577979.html
- 2、【Java Agent探针技术探秘】https://ata.atatech.org/articles/11000162316?spm=ata.23639746.0.0.47507c90Fcc4f4
- 3、【Arthas原理简析及应用】https://ata.atatech.org/articles/11000249240?spm=ata.23639746.0.0.47507c90Ce5mJD#LMKWsXKY
- 4、【Java Attach API】https://www.cnblogs.com/756623607-zhang/p/12575509.html