JVM源碼分析之Attach機制實現徹底解讀

本文來自: PerfMa技術社區java

PerfMa(笨馬網絡)官網linux

Attach是什麼

在講這個以前,咱們先來點你們都知道的東西,當咱們感受線程一直卡在某個地方,想知道卡在哪裏,首先想到的是進行線程dump,而經常使用的命令是jstack ,咱們就能夠看到以下線程棧了安全

image.png

你們是否注意過上面圈起來的兩個線程,」Attach Listener」和「Signal Dispatcher」,這兩個線程是咱們此次要講的Attach機制的關鍵,先偷偷告訴各位,其實Attach Listener這個線程在jvm起來的時候可能並無的,後面會細說。網絡

那Attach機制是什麼?說簡單點就是jvm提供一種jvm進程間通訊的能力,能讓一個進程傳命令給另一個進程,並讓它執行內部的一些操做,好比說咱們爲了讓另一個jvm進程把線程dump出來,那麼咱們跑了一個jstack的進程,而後傳了個pid的參數,告訴它要哪一個進程進行線程dump,既然是兩個進程,那確定涉及到進程間通訊,以及傳輸協議的定義,好比要執行什麼操做,傳了什麼參數等多線程

Attach能作些什麼

總結起來講,好比內存dump,線程dump,類信息統計(好比加載的類及大小以及實例個數等),動態加載agent(使用過btrace的應該不陌生),動態設置vm flag(可是並非全部的flag均可以設置的,由於有些flag是在jvm啓動過程當中使用的,是一次性的),打印vm flag,獲取系統屬性等,這些對應的源碼(AttachListener.cpp)以下jvm

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 }
};
複製代碼

後面是命令對應的處理函數。函數

Attach在jvm裏如何實現的

Attach Listener線程的建立

前面也提到了,jvm在啓動過程當中可能並無啓動Attach Listener這個線程,能夠經過jvm參數來啓動,代碼 (Threads::create_vm)以下:微服務

if (!DisableAttachMechanism) {
    if (StartAttachListener || AttachListener::init_at_startup()) {
      AttachListener::init();
    }
  }
bool AttachListener::init_at_startup() {
  if (ReduceSignalUsage) {
    return true;
  } else {
    return false;
  }
}
複製代碼

其中DisableAttachMechanism,StartAttachListener ,ReduceSignalUsage均默認是false(globals.hpp)學習

product(bool, DisableAttachMechanism, false,                              
         "Disable mechanism that allows tools to Attach to this VM」)   
product(bool, StartAttachListener, false,                                 
          "Always start Attach Listener at VM startup")  
product(bool, ReduceSignalUsage, false,                                   
          "Reduce the use of OS signals in Java and/or the VM」)
複製代碼

所以AttachListener::init()並不會被執行,而Attach Listener線程正是在此方法裏建立的ui

image.png

既然在啓動的時候不會建立這個線程,那麼咱們在上面看到的那個線程是怎麼建立的呢,這個就要關注另一個線程「Signal Dispatcher」了,顧名思義是處理信號的,這個線程是在jvm啓動的時候就會建立的,具體代碼就不說了。

下面以jstack的實現來講明觸發Attach這一機制進行的過程,jstack命令的實現實際上是一個叫作JStack.java的類,查看jstack代碼後會走到下面的方法裏

image.png

請注意VirtualMachine.Attach(pid);這行代碼,觸發Attach pid的關鍵,若是是在linux下會走到下面的構造函數

image.png

這裏要解釋下代碼了,首先看到調用了createAttachFile方法在目標進程的cwd目錄下建立了一個文件/proc//cwd/.Attach_pid,這個在後面的信號處理過程當中會取出來作判斷(爲了安全),另外咱們知道在linux下線程是用進程實現的,在jvm啓動過程當中會建立不少線程,好比咱們上面的信號線程,也就是會看到不少的pid(應該是LWP),那麼如何找到這個信號處理線程呢,從上面實現來看是找到咱們傳進去的pid的父進程,而後給它的全部子進程都發送一個SIGQUIT信號,而jvm裏除了信號線程,其餘線程都設置了對此信號的屏蔽,所以收不到該信號,因而該信號就傳給了「Signal Dispatcher」,在傳完以後做輪詢等待看目標進程是否建立了某個文件,AttachTimeout默認超時時間是5000ms,可經過設置系統變量sun.tools.Attach.AttachTimeout來指定,下面是Signal Dispatcher線程的entry實現

image.png

當信號是SIGBREAK(在jvm裏作了#define,其實就是SIGQUIT)的時候,就會觸發 AttachListener::is_init_trigger()的執行

image.png

一開始會判斷當前進程目錄下是否有個.Attach_pid文件(前面提到了),若是沒有就會在/tmp下建立一個/tmp/.Attach_pid,當那個文件的uid和本身的uid是一致的狀況下(爲了安全)再調用init方法

image.png

此時水落石出了,看到建立了一個線程,而且取名爲Attach Listener。再看看其子類LinuxAttachListener的init方法

image.png

看到其建立了一個監聽套接字,並建立了一個文件/tmp/.java_pid,這個文件就是客戶端以前一直在輪詢等待的文件,隨着這個文件的生成,意味着Attach的過程圓滿結束了。

Attach listener接收請求

看看它的entry實現Attach_listener_thread_entry

image.png

從代碼來看就是從隊列裏不斷取AttachOperation,而後找到請求命令對應的方法進行執行,好比咱們一開始說的jstack命令,找到 { 「threaddump」, thread_dump }的映射關係,而後執行thread_dump方法

再來看看其要調用的AttachListener::dequeue(),

AttachOperation* AttachListener::dequeue() {
  JavaThread* thread = JavaThread::current();
  ThreadBlockInVM tbivm(thread);

  thread->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or
  // java_suspend_self() via check_and_wait_while_suspended()

  AttachOperation* op = LinuxAttachListener::dequeue();

  // were we externally suspended while we were waiting?
  thread->check_and_wait_while_suspended();

  return op;
}
複製代碼

最終調用的是LinuxAttachListener::dequeue(),

image.png

咱們看到若是沒有請求的話,會一直accept在那裏,當來了請求,而後就會建立一個套接字,並讀取數據,構建出LinuxAttachOperation返回並執行。

整個過程就這樣了,從Attach線程建立到接收請求,處理請求。

一塊兒來學習吧

PerfMa KO 系列課之 JVM 參數【Memory篇】

記一次微服務耗時毛刺排查

相關文章
相關標籤/搜索