一、什么是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方法最终是使用了/proc和ptrace来读取目标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.cpp的Perf_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>
网友评论