占小狼,轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!
jmap、jstack等工具可以訪問虛擬機(jī)中的堆對(duì)象、線程信息等,可以通過兩種方式實(shí)現(xiàn)
1、attach方式
2、SA方式
attach方式
這種方式,在之前的文章已經(jīng)分析過,底層通過socket進(jìn)行通信,jmap進(jìn)程好比一個(gè)客戶端,運(yùn)行的虛擬機(jī)看成服務(wù)端,有一個(gè)叫"Attach Listener"的線程,專門負(fù)責(zé)監(jiān)聽attach的請(qǐng)求,并在虛擬機(jī)中執(zhí)行對(duì)應(yīng)的代碼。
更多詳情點(diǎn)擊 jmap命令的實(shí)現(xiàn)原理解析
SA方式
首先,我們先看看SA可以做什么?
1、從運(yùn)行的Java進(jìn)程中讀取數(shù)據(jù)
2、從數(shù)據(jù)中,解析出所有Hotspot數(shù)據(jù)結(jié)構(gòu)
3、從Hotspot數(shù)據(jù)結(jié)構(gòu)中國(guó),解析出所有的Java對(duì)象
這里需要清楚的是:
SA是運(yùn)行在單獨(dú)的進(jìn)程中,和目標(biāo)Java進(jìn)程是隔離的,而且在使用SA工具時(shí),不會(huì)在目標(biāo)Java進(jìn)程中執(zhí)行任何代碼,而是讀取目標(biāo)Java進(jìn)程中的數(shù)據(jù),然后在自身進(jìn)程中處理,在SA讀取數(shù)據(jù)時(shí),目標(biāo)Java進(jìn)程會(huì)被掛起。
那么,SA是如何讀取到目標(biāo)Java進(jìn)程的呢?不同的系統(tǒng),有不同的方式:
1、Solaris系統(tǒng)中,通過 libproc 實(shí)現(xiàn)
2、Linux系統(tǒng)中,通過 /proc和ptrace 實(shí)現(xiàn)
3、Windows系統(tǒng)中,通過 dbgeng.dll library 實(shí)現(xiàn)
下面以Linux為例,看看是如何一步一步實(shí)現(xiàn)的。
假設(shè)執(zhí)行"jmap -heap <pid>",該命令對(duì)應(yīng)的實(shí)現(xiàn)類
"sun.jvm.hotspot.tools.HeapSummary.java"

SA的工具類都繼承了Tool類,通過start方法啟動(dòng),start方法中會(huì)初始化一個(gè)BugSpotAgent,并通過BugSpotAgent的attach方法與目標(biāo)Java進(jìn)程建立聯(lián)系,attach方法實(shí)現(xiàn)如下:

在setupDebugger方法中,根據(jù)不同平臺(tái)初始化debugger,attach動(dòng)作最終由具體的平臺(tái)相關(guān)的JVMDebugger對(duì)象完成,在Linux平臺(tái),使用LinuxDebuggerLocal對(duì)象,LinuxDebuggerLocal類中具體的attach實(shí)現(xiàn)如下:

最終調(diào)用了一個(gè)本地方法
private native void attach0(int pid) throws DebuggerException;
本地方法attach0的實(shí)現(xiàn)位于LinuxDebuggerLocal.c中

在本地方法attach0中,看到有一個(gè)Pgrab方法,跟進(jìn)去...

Pgrab方法的注釋也說明該方法可以attach到目標(biāo)進(jìn)程上,從ptrace_attach方法再跟進(jìn)去...

這里,我們看到了ptrace命令
ptrace(PTRACE_ATTACH, pid, NULL, NULL)
其中PTRACE_ATTACH,可以實(shí)現(xiàn)attach到一個(gè)指定pid的進(jìn)程。
SA成功attach到目標(biāo)Java進(jìn)程之后,執(zhí)行setupVM,初始化Hotspot數(shù)據(jù)結(jié)構(gòu),之后的數(shù)據(jù)獲取通過/proc實(shí)現(xiàn),這里不再深入分析了。