【工具学习】Arthas Java应用监控工具 - hippowc/hippowc.github.io GitHub Wiki

Arthas是Alibaba开源的Java诊断工具 git地址:https://github.com/alibaba/arthas

Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

原理

JVM Attach机制

当我们感觉线程一直卡在某个地方,想知道卡在哪里,首先想到的是进行线程dump,而常用的命令是jstack ,就可以看到线程栈了;可能会看到两个线程:”Attach Listener”和“Signal Dispatcher”,Attach Listener这个线程在jvm起来的时候可能并没有的;

Attach机制说简单点就是jvm提供一种jvm进程间通信的能力,能让一个进程传命令给另外一个进程,并让它执行内部的一些操作,比如说我们为了让另外一个jvm进程把线程dump出来,那么我们跑了一个jstack的进程,然后传了个pid的参数,告诉它要哪个进程进行线程dump,既然是两个进程,那肯定涉及到进程间通信,以及传输协议的定义,比如要执行什么操作,传了什么参数等

可以做什么

内存dump,线程dump,类信息统计(比如加载的类及大小以及实例个数等),动态加载agent(使用过btrace的应该不陌生),动态设置vm flag(但是并不是所有的flag都可以设置的,因为有些flag是在jvm启动过程中使用的,是一次性的),打印vm flag,获取系统属性等,这些对应的源码(AttachListener.cpp)如下

static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties",  get_agent_properties },
  { "datadump",         data_dump },
  { "dumpheap",         dump_heap },
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",       thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { "jcmd",             jcmd },
  { NULL,               NULL }
};

Attach的启动

jvm在启动过程中可能并没有启动Attach Listener这个线程,可以通过jvm参数来启动;默认Attack Listener不会启动;但是另外一个线程“Signal Dispatcher”在jvm启动的时候就会创建的

以jstack的实现来说明触发Attach这一机制进行的过程,jstack命令的实现其实是一个叫做JStack.java的类,我们精简下代码

VirtualMachine vm = null;
vm = VirtualMachine.Attach(pid);
InputStream in = ((HotSpotVirtualMachine)vm).remoteDataDump((Object[])args);
// 打印信息      

linux下线程是用进程实现的,在jvm启动过程中会创建很多线程,比如我们上面的信号线程,也就是会看到很多的pid(应该是LWP),那么如何找到这个信号处理线程呢,从上面实现来看是找到我们传进去的pid的父进程,然后给它的所有子进程都发送一个SIGQUIT信号,而jvm里除了信号线程,其他线程都设置了对此信号的屏蔽,因此收不到该信号,于是该信号就传给了“Signal Dispatcher”

看到创建了一个线程,并且取名为Attach Listener,创建了一个监听套接字,并创建了一个文件/tmp/.java_pid,这个文件就是客户端之前一直在轮询等待的文件,随着这个文件的生成,意味着Attach的过程圆满结束了。

Attach接受请求

Attach可以执行的命令是规定好的,如AttachOperationFunctionInfo这个文件所示

从队列里不断取AttachOperation,然后找到请求命令对应的方法进行执行,比如我们一开始说的jstack命令,找到 { “threaddump”, thread_dump }的映射关系,然后执行thread_dump方法

如果没有请求的话,会一直accept在那里,当来了请求,然后就会创建一个套接字,并读取数据,构建出LinuxAttachOperation返回并执行。

Instrumentation -- 仪表

java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力

Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动

但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的

Instrumentation 的基本功能和用法

“java.lang.instrument”包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的

而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。

“agentmain”方法,可以在 main 函数开始运行之后再运行。开发者必须在 manifest 文件里面设置“Agent-Class”来指定包含 agentmain 函数的类。agentmain 需要在 main 函数开始运行后才启动,这样的时机应该如何确定呢,这样的功能又如何实现呢?

这就是 Java SE 6 当中提供的 Attach API。Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面: VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等,整个过程的伪代码如下

// 模拟一个类替换的过程

// 被替换的类
class xxx{}
// 编写agent,这个要被打成一个jar包
public static void agentmain
// 编写Attach程序,将agent加载进去
vm = VirtualMachine.attach(vmd);
vm.loadAgent(jar);

Arthas实现

基于以上两个特性,是的线上监控成为可能。agent被加载到目标应用之后:

  • 把Arthas所包含的jar包加入到目标java进程的BootstrapClassLoader中;
  • 生成一个Arthas专用的classloader来加载Arthas类;
  • 使用Arthas专用classloader来加载一些必须的类,其中最重要的是com.taobao.arthas.core.server.ArthasServer

ArthasServer这个server就是应用中的【间谍】,可以在应用中做任何事情。一个重要的应用:AdviceWeaver;AdviceWeaver继承自ASM的ClassVisitor可以对代码进行增强。

是否会有副作用

织入的代码在agent断开连接后是否会解除?应该是可以的,应为是动态织入,代理类没有了,那么增强类也会消失,具体需要查看ASM如何实现

类似的神器 btrace