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机制响应,从而创建连接会话,而后根据请求返回数据。