Arthas技术原理-源码整体结构


预备知识

类似JVM中的AOP
Java的agent机制

Instructation机制

使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。
有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,
这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,
使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
-- 百度百科

instrument的底层实现依赖于JVMTI,也就是JVM Tool Interface,它是JVM暴露出来的一些供用户扩展的接口集合。
JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
JVMTI机制

使用instrument 的方式有两种,下面分别简单总结一下:

premain方式(JVM启动前进行增强)

需要实现下面的方法

public static void premain(String args, Instrumentation instrumentation){
    //在这里调用instrumentation 做增强操作
}

并在打包jar的时候,指定Premain-Class 的入口类

可以参考arthas中arthas-agent/pom.xml

实现premain的例子

运行原理如下图:
premain反射的运行原理

在目标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实例进行增强

运行原理如下所示:
agentmain方式运行原理

常见的应用案例:

arthas

arthas核心模块的功能和流程

arthas的核心模块不多,主要是以下几个:

arthas-common

类如下所示:
arthas-common的类

主要是存放公共类、工具类
如io相关的、文件相关的、反射相关的工具

arthas-apy

arthas-spy模块的运行示意图

只有一个类
arthas-spy类截图

定义此类,用于依附到目标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中

        加载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的入口

参考


文章作者: 王利康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王利康 !
  目录