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機制響應,從而創建連接會話,而後根據請求返回數據。