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

相关文章
相关标签/搜索