一、什么是Attach機制庸追?
簡單點說就是jdk的一些工具類提供的一種jvm進程間通信的能力衅谷,能讓一個進程傳命令給另外一個進程,并讓它執(zhí)行內(nèi)部的一些操作窑眯,比如說我們?yōu)榱俗屃硗庖粋€jvm進程把線程dump出來,那么我們運行了一個jstack的進程医窿,然后給它傳了個pid的參數(shù)磅甩,告訴它要對哪個進程進行線程dump,既然是兩個進程姥卢,那肯定涉及到進程間通信卷要,以及傳輸協(xié)議的定義,比如要執(zhí)行什么操作独榴,傳了什么參數(shù)等等僧叉。
Attach機制可以對目標(biāo)進程收集很多信息,如內(nèi)存dump棺榔,線程dump彪标,類信息統(tǒng)計(比如加載的類及大小以及實例個數(shù)等),動態(tài)加載agent掷豺,動態(tài)設(shè)置vm flag(但是并不是所有的flag都可以設(shè)置的捞烟,因為有些flag是在jvm啟動過程中使用的薄声,是一次性的),打印vm flag题画,獲取系統(tǒng)屬性等等默辨,這些對應(yīng)的源碼(AttachListener.cpp)。
二苍息、Attach方法小結(jié)
1缩幸、繼承Tool/HotSpotAgent.attach(采用Serviceability?Agent,簡稱SA)
SA(Serviceability?Agent)是一個用于分析HotSpot運行時進程和Core文件中數(shù)據(jù)的工具竞思。它可以attach到Java進程或分析Core文件中的數(shù)據(jù)表谊,了解加載的class,是一個包含大量Java API和工具的工具集盖喷,目前實現(xiàn)只支持“snapshot”式的使用方式爆办。“snapshot”是指不支持在SA保持連接的同時讓目標(biāo)進程運行课梳,就是說無論如何在SA進行attach的時候目標(biāo)進程都要暫停的(SA在attatch到進程之后距辆,會暫停當(dāng)前進程的執(zhí)行,拿到的是進程的一個snapshot暮刃,當(dāng)前進程會在SA斷開后繼續(xù)執(zhí)行)跨算,所以在線上使用這類工具進行dump時無論耗時長短必須要摘流量,否則可能會使服務(wù)不可用而帶來一些不必要的影響椭懊。
SA 在JDK中是以Jar文件的形式提供的诸蚕,位于JAVA_HOME/lib/sa-jdi.jar?,和一般的Jar文件執(zhí)行一樣氧猬。
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編譯出來的可執(zhí)行文件是elf文件)烫沙;
(2)接著通過保存的符號表讀取HotSpotVM中l(wèi)ocalHotSpotVMStructs和localHotSpotVMTypes等變量的地址;
(3)然后使用ptrace根據(jù)變量的地址讀取SA需要用到的HotSpotVM中的數(shù)據(jù)的元信息(類型信息隙笆,字段offset锌蓄,地址等)升筏;
(4)最后根據(jù)這些元信息就可以讀取到目標(biāo)VM上這些數(shù)據(jù)的值。
在Linux平臺上瘸爽,attach方法最終是使用了/proc和ptrace來讀取目標(biāo)VM中的數(shù)據(jù)您访,ptrace提供了一種使父進程可以監(jiān)視和控制其它進程的方式,它還能夠改變子進程中的寄存器和內(nèi)核映像剪决,因而可以實現(xiàn)斷點調(diào)試和系統(tǒng)調(diào)用的跟蹤(ptrace會使內(nèi)核暫停當(dāng)前進程并將控制權(quán)交給跟蹤進程灵汪,使跟蹤進程得以察看或者修改被跟蹤進程的寄存器,待收集完跟蹤信息以后會把控制權(quán)交回給當(dāng)前進程讓其繼續(xù)運行)柑潦。?
SA工具的attach和detach分別對應(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方法過程分析(linux):
(1)信號機制
JVM啟動的時候并不會馬上創(chuàng)建Attach Listener線程渗鬼,而是通過另外一個線程Signal Dispatcher在接收到信號處理請求(如jstack览露,jmap等)時創(chuàng)建臨時socket文件/tmp/.java_pid并創(chuàng)建Attach Listener線程(external process會先發(fā)送一個SIGQUIT信號給target VM process,target VM會創(chuàng)建一個Attach Listener線程)乍钻;
(2)Unix domain socket
Attach Listener線程會通過Unix domain socket與external process建立連接肛循,之后就可以基于這個socket進行通信了。
創(chuàng)建好的Attach Listener線程會負(fù)責(zé)執(zhí)行這些命令(從隊列里不斷取AttachOperation银择,然后找到請求命令對應(yīng)的方法進行執(zhí)行多糠,比如jstack命令,找到 { “threaddump”, thread_dump }的映射關(guān)系浩考,然后執(zhí)行thread_dump方法)并且把結(jié)果通過.java_pid文件返回給發(fā)送者夹孔。
????? 整個過程中,會有兩個文件被創(chuàng)建:
.attach_pid<pid>析孽,external process會創(chuàng)建這個文件搭伤,為的是觸發(fā)Attach Listener線程的創(chuàng)建,因為SIGQUIT信號不是只有external process才會發(fā)的袜瞬,通過這個文件來告訴target VM怜俐,有attach請求過來了(如果.attach_pid創(chuàng)建好了,說明Attach Listener線程已經(jīng)創(chuàng)建成功)邓尤。相關(guān)代碼在LinuxVirtualMachine.java中拍鲤;
.java_pid<pid>,target VM會創(chuàng)建這個文件汞扎,這個是因為Unix domain socket本身的實現(xiàn)機制需要去創(chuàng)建一個文件季稳,通過這個文件來進行IPC。相關(guān)代碼在attachListener_linux.cpp中澈魄。
其中的<pid>都是target VM的pid景鼠。
具體更詳細(xì)的VirtualMachine.attach的源碼分析見:VirtualMachine.attach源碼分析
???? Attach Listener線程命令對應(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(通過PerfData文件獲取信息)
用lsof -p 查看進程打開了哪些文件時痹扇,經(jīng)愁趵欤可以看到/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)用過程:
調(diào)用rt.jar包的sun.misc.Perf類的attach方法
--->調(diào)用對應(yīng)的perf.cpp的Perf_Attach方法--->方法里再調(diào)用PerfMemory::attach
--->最后通過方法mmap_attach_shared將GC或其他狀態(tài)相關(guān)的數(shù)據(jù)寫入到該mmap內(nèi)存映射文件(該mmap內(nèi)存映射文件是在JVM啟動時調(diào)用PerfMemory::create_memory_region就已經(jīng)創(chuàng)建好的)杜窄。
?jstat肠骆,sjk等工具通過訪問該mmap內(nèi)存映射文件,讀取到相關(guān)的內(nèi)容塞耕,顯示在屏幕上蚀腿。
4、幾種命令工具的attach方式的比較
(1)幾種attach方式的比較:
(2)命令工具以及它的所屬系列及對應(yīng)的代碼入口:
(3)命令工具以及它所對應(yīng)的attach方式:
jmap和jstack的“-F”參數(shù)可以把原先VirtualMachine.attach方式強制改為SA attach方式扫外,命令如下:
jmap -F -histo <pid>
jstack -F <pid>
jstack -F -l <pid>