相信Java程序员对jstack都不陌生,今天我们坑进OpenJDK源码来看看jstack是怎么实现的。
通过官方文档的介绍,我们可以知道jstack的源码在OpenJDK的jdk子项目下,sun.tools.jstack.JStack
,
/*
* This class is the main class for the JStack utility. It parses its arguments
* and decides if the command should be executed by the SA JStack tool or by
* obtained the thread dump from a target process using the VM attach mechanism
*/
public class JStack { ... }
通过注释我们可以知道,jstack会根据不同参数决定使用不同的方式来进行thread dump:使用SA的JStack,或者使用attach机制。
通过代码可以看到,下面三种情况会使用SA的JStack,
- 使用了
-m
选项; - 使用了
-F
选项; - 不是对本地进程而是对远程server执行jstack;
下面我们来看下使用attach机制实现的thread dump(SA的实现就先欠着啦:)。
关于attach机制,暴露给用户的其实就是Attach API,其底层实现,简单来讲就是在虚拟机启动了一个Attach Listener,外部进程通过UNIX socket与其进行通信,发送一些命令让Attach Listener去执行。具体可以参考官方文档或者这一篇博文。
下面我们来看下jstack是如何使用attach机制的。runThreadDump
方法,
// Attach to pid and perform a thread dump
private static void runThreadDump(String pid, String args[]) throws Exception {
VirtualMachine vm = null;
try {
///
// 使用 Attach API
///
vm = VirtualMachine.attach(pid);
} catch (Exception x) {
String msg = x.getMessage();
if (msg != null) {
System.err.println(pid + ": " + msg);
} else {
x.printStackTrace();
}
if ((x instanceof AttachNotSupportedException) &&
(loadSAClass() != null)) {
System.err.println("The -F option can be used when the target " +
"process is not responding");
}
System.exit(1);
}
///
// 发送命令执行thread dump
///
// Cast to HotSpotVirtualMachine as this is implementation specific
// method.
InputStream in = ((HotSpotVirtualMachine)vm).remoteDataDump((Object[])args);
// read to EOF and just print output
byte b[] = new byte[256];
int n;
do {
n = in.read(b);
if (n > 0) {
String s = new String(b, 0, n, "UTF-8");
System.out.print(s);
}
} while (n > 0);
in.close();
vm.detach();
}
可以看到最后是通过HotSpotVirtualMachine#remoteDataDump
来执行thread dump。来看下这个方法,
// Remote ctrl-break. The output of the ctrl-break actions can
// be read from the input stream.
public InputStream remoteDataDump(Object ... args) throws IOException {
return executeCommand("threaddump", args);
}
private InputStream executeCommand(String cmd, Object ... args) throws IOException {
try {
return execute(cmd, args);
} catch (AgentLoadException x) {
throw new InternalError("Should not get here", x);
}
}
继续往下跟代码到Linux平台上的实现,LinuxVirtualMachine#execute
,可以看到最终通过UNIX socket与Attach Listener进行了通信,发送的就是上面看到的threaddump
命令。Attach Listener对接收到的命令所执行的操作,在attachListener.cpp,
// names must be of length <= AttachOperation::name_length_max
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 }
};
看下threaddump
所对应的thread_dump
方法,
// 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;
}
所以再往下就得坑进HotSpot的源码,VMThread::execute
,才能看到最终HotSpot是怎么执行的thread dump了。这里就不继续挖坑了,感兴趣的同学可以继续跟下去 ^_^
嗯,今天就先到这,have fun la