Java Attach机制

一、什么是Attach机制?

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

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


二、Attach方法小结

1、继承Tool/HotSpotAgent.attach(采用Serviceability Agent,简称SA)

SA(Serviceability Agent)是一个用于分析HotSpot运行时进程和Core文件中数据的工具。它可以attach到Java进程或分析Core文件中的数据,了解加载的class,是一个包含大量Java API和工具的工具集,目前实现只支持“snapshot”式的使用方式。“snapshot”是指不支持在SA保持连接的同时让目标进程运行,就是说无论如何在SA进行attach的时候目标进程都要暂停的(SA在attatch到进程之后,会暂停当前进程的执行,拿到的是进程的一个snapshot,当前进程会在SA断开后继续执行),所以在线上使用这类工具进行dump时无论耗时长短必须要摘流量,否则可能会使服务不可用而带来一些不必要的影响。

SA 在JDK中是以Jar文件的形式提供的,位于JAVA_HOME/lib/sa-jdi.jar ,和一般的Jar文件执行一样。

TBJMap使用了hotspot源码的sa-jdi.jar的sun.jvm.hotspot.HotSpotAgent这个类(其中TBJMap继承了sun.jvm.hotspot.tools.Tool这个类,最终用到的也是HotSpotAgent作为代理agent,也就是使用的是SA)。

HotSpotAgent.attach方法过程分析(linux):

(1)首先通过/proc/[pid]/maps读取elf文件,保存符号表(elf文件除了机器码外,还包含其它额外的信息,如段的加载地址,运行地址,重定位表,符号表等,比bin文件要大,通过gcc编译出来的可执行文件是elf文件);

(2)接着通过保存的符号表读取HotSpotVM中localHotSpotVMStructs和localHotSpotVMTypes等变量的地址;

(3)然后使用ptrace根据变量的地址读取SA需要用到的HotSpotVM中的数据的元信息(类型信息,字段offset,地址等);

(4)最后根据这些元信息就可以读取到目标VM上这些数据的值。

在Linux平台上,attach方法最终是使用了/procptrace来读取目标VM中的数据,ptrace提供了一种使父进程可以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪(ptrace会使内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器,待收集完跟踪信息以后会把控制权交回给当前进程让其继续运行)。 

SA工具的attach和detach分别对应的ptrace方法是:

ptrace(PT_ATTACH, pid, 0, 0);  
ptrace(PT_DETACH, pid, 0, 0);
​​更具体源码分析见:HotSpotAgent.attach源码分析

2、VirtualMachine.attach(Attach到Attach Listener线程后执行有限命令)

jstack和jhipcup的attach使用的是VirtualMachine.attach。

VirtualMachine.attach方法过程分析(linux):

(1)信号机制

JVM启动的时候并不会马上创建Attach Listener线程,而是通过另外一个线程Signal Dispatcher在接收到信号处理请求(如jstack,jmap等)时创建临时socket文件/tmp/.java_pid并创建Attach Listener线程(external process会先发送一个SIGQUIT信号给target VM process,target VM会创建一个Attach Listener线程);

(2)Unix domain socket

Attach Listener线程会通过Unix domain socket与external process建立连接,之后就可以基于这个socket进行通信了。

创建好的Attach Listener线程会负责执行这些命令(从队列里不断取AttachOperation,然后找到请求命令对应的方法进行执行,比如jstack命令,找到 { “threaddump”, thread_dump }的映射关系,然后执行thread_dump方法)并且把结果通过.java_pid文件返回给发送者。

      整个过程中,会有两个文件被创建:

.attach_pid<pid>,external process会创建这个文件,为的是触发Attach Listener线程的创建,因为SIGQUIT信号不是只有external process才会发的,通过这个文件来告诉target VM,有attach请求过来了(如果.attach_pid创建好了,说明Attach Listener线程已经创建成功)。相关代码在LinuxVirtualMachine.java中;

.java_pid<pid>,target VM会创建这个文件,这个是因为Unix domain socket本身的实现机制需要去创建一个文件,通过这个文件来进行IPC。相关代码在attachListener_linux.cpp中。

其中的<pid>都是target VM的pid。

具体更详细的VirtualMachine.attach的源码分析见:VirtualMachine.attach源码分析

     Attach Listener线程命令对应的源码(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 }
};

3、Perf.getPerf().attach(通过PerfData文件获取信息)

用lsof -p 查看进程打开了哪些文件时,经常可以看到/tmp/hsperfdata_$username/$pid文件,如:

[root@ospdev-qxtjx]# lsof -p 32098 | grep perf
java 32098 root  mem    REG   252,1   32768  934145 /tmp/hsperfdata_root/32098
​该文件其实是一个mmap内存映射文件,JVM用来收集状态数据给其它进程使用的,可以使用-XX:+PerfDisableSharedMem来关闭它,当到达安全点时,JVM会把安全点的相关信息写入到这个文件中去,在使用jstack,jconsole等工具时会读取该文件获取内容来统计信息。

perf attach源码调用过程:

调用rt.jar包的sun.misc.Perf类的attach方法

--->调用对应的perf.cppPerf_Attach方法--->方法里再调用PerfMemory::attach

--->最后通过方法mmap_attach_shared将GC或其他状态相关的数据写入到该mmap内存映射文件(该mmap内存映射文件是在JVM启动时调用PerfMemory::create_memory_region就已经创建好的)。

 jstat,sjk等工具通过访问该mmap内存映射文件,读取到相关的内容,显示在屏幕上。

4、几种命令工具的attach方式的比较

(1)几种attach方式的比较:

几种attach方式的比较

(2)命令工具以及它的所属系列及对应的代码入口:

命令工具以及它的所属系列及对应的代码入口

(3)命令工具以及它所对应的attach方式:

命令工具以及它所对应的attach方式

jmap和jstack的“-F”参数可以把原先VirtualMachine.attach方式强制改为SA attach方式,命令如下:

jmap -F -histo <pid>
jstack -F <pid>
jstack -F -l <pid>


参考文献:

1、JVM源码分析之Attach机制实现完全解读(你假笨)

2、HotSpot Serviceability Agent 实现浅析

推荐阅读更多精彩内容

  • Attach是什么 在讲这个之前,我们先来点大家都知道的东西,当我们感觉线程一直卡在某个地方,想知道卡在哪里,首先...
    jerrik阅读 272评论 0 1
  • 从JDK6开始引入,除了Solaris平台的Sun JVM支持远程的Attach,在其他平台都只允许Attach到...
    andersonoy阅读 1,649评论 0 2
  • Linux SignalsStandard SignalsLinux supports the standard ...
    andersonoy阅读 148评论 0 0
  • 最近因为需求,这周学习了html的一些内容。整理了下常用的这些标签。
    OSong阅读 25评论 0 0
  • 一件蓝布褂 满头银丝 两只浑浊的眼睛 右手拄着一根拐棍 左手端着葫芦瓢 从远处蹒跚而来 我的大姥姥 爬到树上顽皮的...
    沐叶_93f1阅读 53评论 0 9