一、什么是Attach機(jī)制?
簡(jiǎn)單點(diǎn)說(shuō)就是jdk的一些工具類提供的一種jvm進(jìn)程間通信的能力,能讓一個(gè)進(jìn)程傳命令給另外一個(gè)進(jìn)程,并讓它執(zhí)行內(nèi)部的一些操作,比如說(shuō)我們?yōu)榱俗屃硗庖粋€(gè)jvm進(jìn)程把線程dump出來(lái),那么我們運(yùn)行了一個(gè)jstack的進(jìn)程,然后給它傳了個(gè)pid的參數(shù),告訴它要對(duì)哪個(gè)進(jìn)程進(jìn)行線程dump,既然是兩個(gè)進(jìn)程,那肯定涉及到進(jìn)程間通信,以及傳輸協(xié)議的定義,比如要執(zhí)行什么操作,傳了什么參數(shù)等等。
Attach機(jī)制可以對(duì)目標(biāo)進(jìn)程收集很多信息,如內(nèi)存dump,線程dump,類信息統(tǒng)計(jì)(比如加載的類及大小以及實(shí)例個(gè)數(shù)等),動(dòng)態(tài)加載agent,動(dòng)態(tài)設(shè)置vm flag(但是并不是所有的flag都可以設(shè)置的,因?yàn)橛行ゝlag是在jvm啟動(dòng)過(guò)程中使用的,是一次性的),打印vm flag,獲取系統(tǒng)屬性等等,這些對(duì)應(yīng)的源碼(AttachListener.cpp)。
二、Attach方法小結(jié)
1、繼承Tool/HotSpotAgent.attach(采用Serviceability?Agent,簡(jiǎn)稱SA)
SA(Serviceability?Agent)是一個(gè)用于分析HotSpot運(yùn)行時(shí)進(jìn)程和Core文件中數(shù)據(jù)的工具。它可以attach到Java進(jìn)程或分析Core文件中的數(shù)據(jù),了解加載的class,是一個(gè)包含大量Java API和工具的工具集,目前實(shí)現(xiàn)只支持“snapshot”式的使用方式?!皊napshot”是指不支持在SA保持連接的同時(shí)讓目標(biāo)進(jìn)程運(yùn)行,就是說(shuō)無(wú)論如何在SA進(jìn)行attach的時(shí)候目標(biāo)進(jìn)程都要暫停的(SA在attatch到進(jìn)程之后,會(huì)暫停當(dāng)前進(jìn)程的執(zhí)行,拿到的是進(jìn)程的一個(gè)snapshot,當(dāng)前進(jìn)程會(huì)在SA斷開(kāi)后繼續(xù)執(zhí)行),所以在線上使用這類工具進(jìn)行dump時(shí)無(wú)論耗時(shí)長(zhǎng)短必須要摘流量,否則可能會(huì)使服務(wù)不可用而帶來(lái)一些不必要的影響。
SA 在JDK中是以Jar文件的形式提供的,位于JAVA_HOME/lib/sa-jdi.jar?,和一般的Jar文件執(zhí)行一樣。
TBJMap使用了hotspot源碼的sa-jdi.jar的sun.jvm.hotspot.HotSpotAgent這個(gè)類(其中TBJMap繼承了sun.jvm.hotspot.tools.Tool這個(gè)類,最終用到的也是HotSpotAgent作為代理agent,也就是使用的是SA)。
HotSpotAgent.attach方法過(guò)程分析(linux):
(1)首先通過(guò)/proc/[pid]/maps讀取elf文件,保存符號(hào)表(elf文件除了機(jī)器碼外,還包含其它額外的信息,如段的加載地址,運(yùn)行地址,重定位表,符號(hào)表等,比bin文件要大,通過(guò)gcc編譯出來(lái)的可執(zhí)行文件是elf文件);
(2)接著通過(guò)保存的符號(hào)表讀取HotSpotVM中l(wèi)ocalHotSpotVMStructs和localHotSpotVMTypes等變量的地址;
(3)然后使用ptrace根據(jù)變量的地址讀取SA需要用到的HotSpotVM中的數(shù)據(jù)的元信息(類型信息,字段offset,地址等);
(4)最后根據(jù)這些元信息就可以讀取到目標(biāo)VM上這些數(shù)據(jù)的值。
在Linux平臺(tái)上,attach方法最終是使用了/proc和ptrace來(lái)讀取目標(biāo)VM中的數(shù)據(jù),ptrace提供了一種使父進(jìn)程可以監(jiān)視和控制其它進(jìn)程的方式,它還能夠改變子進(jìn)程中的寄存器和內(nèi)核映像,因而可以實(shí)現(xiàn)斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用的跟蹤(ptrace會(huì)使內(nèi)核暫停當(dāng)前進(jìn)程并將控制權(quán)交給跟蹤進(jìn)程,使跟蹤進(jìn)程得以察看或者修改被跟蹤進(jìn)程的寄存器,待收集完跟蹤信息以后會(huì)把控制權(quán)交回給當(dāng)前進(jìn)程讓其繼續(xù)運(yùn)行)。?
SA工具的attach和detach分別對(duì)應(yīng)的ptrace方法是:
ptrace(PT_ATTACH, pid, 0, 0);
ptrace(PT_DETACH, pid, 0, 0);
2、VirtualMachine.attach(Attach到Attach Listener線程后執(zhí)行有限命令)
jstack和jhipcup的attach使用的是VirtualMachine.attach。
VirtualMachine.attach方法過(guò)程分析(linux):
(1)信號(hào)機(jī)制
JVM啟動(dòng)的時(shí)候并不會(huì)馬上創(chuàng)建Attach Listener線程,而是通過(guò)另外一個(gè)線程Signal Dispatcher在接收到信號(hào)處理請(qǐng)求(如jstack,jmap等)時(shí)創(chuàng)建臨時(shí)socket文件/tmp/.java_pid并創(chuàng)建Attach Listener線程(external process會(huì)先發(fā)送一個(gè)SIGQUIT信號(hào)給target VM process,target VM會(huì)創(chuàng)建一個(gè)Attach Listener線程);
(2)Unix domain socket
Attach Listener線程會(huì)通過(guò)Unix domain socket與external process建立連接,之后就可以基于這個(gè)socket進(jìn)行通信了。
創(chuàng)建好的Attach Listener線程會(huì)負(fù)責(zé)執(zhí)行這些命令(從隊(duì)列里不斷取AttachOperation,然后找到請(qǐng)求命令對(duì)應(yīng)的方法進(jìn)行執(zhí)行,比如jstack命令,找到 { “threaddump”, thread_dump }的映射關(guān)系,然后執(zhí)行thread_dump方法)并且把結(jié)果通過(guò).java_pid文件返回給發(fā)送者。
????? 整個(gè)過(guò)程中,會(huì)有兩個(gè)文件被創(chuàng)建:
.attach_pid<pid>,external process會(huì)創(chuàng)建這個(gè)文件,為的是觸發(fā)Attach Listener線程的創(chuàng)建,因?yàn)镾IGQUIT信號(hào)不是只有external process才會(huì)發(fā)的,通過(guò)這個(gè)文件來(lái)告訴target VM,有attach請(qǐng)求過(guò)來(lái)了(如果.attach_pid創(chuàng)建好了,說(shuō)明Attach Listener線程已經(jīng)創(chuàng)建成功)。相關(guān)代碼在LinuxVirtualMachine.java中;
.java_pid<pid>,target VM會(huì)創(chuàng)建這個(gè)文件,這個(gè)是因?yàn)閁nix domain socket本身的實(shí)現(xiàn)機(jī)制需要去創(chuàng)建一個(gè)文件,通過(guò)這個(gè)文件來(lái)進(jìn)行IPC。相關(guān)代碼在attachListener_linux.cpp中。
其中的<pid>都是target VM的pid。
具體更詳細(xì)的VirtualMachine.attach的源碼分析見(jiàn):VirtualMachine.attach源碼分析
???? Attach Listener線程命令對(duì)應(yīng)的源碼(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(通過(guò)PerfData文件獲取信息)
用lsof -p 查看進(jìn)程打開(kāi)了哪些文件時(shí),經(jīng)??梢钥吹?b>/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
perf attach源碼調(diào)用過(guò)程:
調(diào)用rt.jar包的sun.misc.Perf類的attach方法
--->調(diào)用對(duì)應(yīng)的perf.cpp的Perf_Attach方法--->方法里再調(diào)用PerfMemory::attach
--->最后通過(guò)方法mmap_attach_shared將GC或其他狀態(tài)相關(guān)的數(shù)據(jù)寫(xiě)入到該mmap內(nèi)存映射文件(該mmap內(nèi)存映射文件是在JVM啟動(dòng)時(shí)調(diào)用PerfMemory::create_memory_region就已經(jīng)創(chuàng)建好的)。
?jstat,sjk等工具通過(guò)訪問(wèn)該mmap內(nèi)存映射文件,讀取到相關(guān)的內(nèi)容,顯示在屏幕上。
4、幾種命令工具的attach方式的比較
(1)幾種attach方式的比較:

(2)命令工具以及它的所屬系列及對(duì)應(yīng)的代碼入口:

(3)命令工具以及它所對(duì)應(yīng)的attach方式:

jmap和jstack的“-F”參數(shù)可以把原先VirtualMachine.attach方式強(qiáng)制改為SA attach方式,命令如下:
jmap -F -histo <pid>
jstack -F <pid>
jstack -F -l <pid>