Java內置工具包tools.jar(二)sun.tools.jstack.JStack

        jstack命令或許是java開發人員在排查問題最經常使用的命令之一,它輸出了當前時刻指定進程中java線程的堆棧信息。咱們從jstack開始閱讀,它的入口在sun.tools.jstack.JStack中。java

       在參數校驗的邏輯以後,咱們發現有兩個入口 runJStackTool 和 runThreadDump ,這裏涉及到兩種實現。runJStackTool 是SA的jstack實現,因爲它是在進程以外的審視工具,因此jstack在普通模式導不出數據時(hung)加上-F參數便可使用它來導出進程信息。它的實現本文不作解釋(TODO)。在runThreadDump中咱們看到 linux

VirtualMachine.attach(pid)

這裏是獲取進程信息的關鍵,這裏使用的就是Hotspot的attach,它提供了進程之間互相通訊的機制,再這裏能夠描述成爲jstack命令和目標jvm進程之間的通訊鏈接。如何進行鏈接,先來看一份jstack結果中的兩個線程:安全

"Attach Listener" daemon prio=10 tid=0x000000000755f800 nid=0xe74 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Signal Dispatcher" daemon prio=10 tid=0x0000000007563000 nid=0xa00 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

attach和這兩個線程有何關係,請attach的linux實現LinuxVirtualMachine,在attach(pid)中,它作了下面幾個事情:併發

    一、查找pid文件。jvm

    二、不存在pid文件則建立attach文件併發起進程命令 LinuxVirtualMachine#sendQuitTo socket

static void SendQuitCallback(const pid_t pid, void* user_data) {
    SendQuitContext* context = (SendQuitContext*)user_data;
    pid_t parent = getParent(pid);
    if (parent == context->ppid) {
        kill(pid, SIGQUIT);
    }
}

/*
 * Class:     sun_tools_attach_LinuxVirtualMachine
 * Method:    sendQuitToChildrenOf
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_sun_tools_attach_LinuxVirtualMachine_sendQuitToChildrenOf
  (JNIEnv *env, jclass cls, jint pid)
{
    SendQuitContext context;
    context.ppid = (pid_t)pid;

    /*
     * Iterate over all children of 'pid' and send a QUIT signal to each.
     */
    forEachProcess(SendQuitCallback, (void*)&context);
}

能夠看到實際是向linux進程發起了kill(pid, SIGQUIT);信號。該信號在jvm中只有一個線程在監聽:那就是上圖中的「Signal Dispatcher」,能夠從監聽代碼中獲得一些信息:工具

即執行了AttachListener::is_init_trigger(),在AttachListener的初始化中咱們看到了一行熟悉的名字:ui

const char thread_name[] = "Attach Listener";

沒錯,就是建立了一個新的線程即Attach Listener。該線程在init之後隨即建立了一個文件sprintf(fn, ".attach_pid%d", os::current_process_id()); PID文件以及監聽了該文件的socket端口,於此同時,attach(pid)作了第三件事:this

三、循環等待直至發現由Attach Listener建立的pid文件。spa

四、檢查文件權限,創建socket通道。

至此Attach和jstack「撩上了」。

接下來是執行了方法JStack#runThreadDump,能夠看到向pid文件中發起了數據寫入

this.writeString(s, "1");
this.writeString(s, "threaddump");

從attach方法表中對應的方法有以下定義:

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 }
};

對應的方法中使用VM_PrintThreads打印了線程信息,而後返回給jstack。值得注意的是,在導出線程信息時須要全部線程位於安全點(is_at_safepoint),此刻全部線程將阻塞,而後獲取全部線程數據後釋放線程鎖,格式化輸出。

// Implementation of "threaddump" command - essentially a remote ctrl-break
// See also: ThreadDumpDCmd class
//
static jint thread_dump(AttachOperation* op, outputStream* out) {
  bool print_concurrent_locks = false;
  if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
    print_concurrent_locks = true;
  }

  // thread stacks
  VM_PrintThreads op1(out, print_concurrent_locks);
  VMThread::execute(&op1);

  // JNI global handles
  VM_PrintJNI op2(out);
  VMThread::execute(&op2);

  // Deadlock detection
  VM_FindDeadlocks op3(out);
  VMThread::execute(&op3);

  return JNI_OK;
}

至此,線程信息就輸出到你的眼前了。

    總結下來,由外部線程(jstack)發起握手命令(SIGQUIT),目標jvm監聽該命令後啓動attach機制,外部線程檢查attch機制響應,從而創建連接會話,而後根據請求返回數據。

相關文章
相關標籤/搜索