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

        jstack命令或许是java开发人员在排查问题最常用的命令之一,它输出了当前时刻指定进程中java线程的堆栈信息。我们从jstack开始阅读,它的入口在sun.tools.jstack.JStack中。

       在参数校验的逻辑之后,我们发现有两个入口 runJStackTool 和 runThreadDump ,这里涉及到两种实现。runJStackTool 是SA的jstack实现,由于它是在进程之外的审视工具,所以jstack在普通模式导不出数据时(hung)加上-F参数即可使用它来导出进程信息。它的实现本文不做解释(TODO)。在runThreadDump中我们看到 

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)中,它做了下面几个事情:

    1、查找pid文件。

    2、不存在pid文件则创建attach文件并发起进程命令 LinuxVirtualMachine#sendQuitTo 

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”,可以从监听代码中得到一些信息:

221939_YWgg_859433.png

即执行了AttachListener::is_init_trigger(),在AttachListener的初始化中我们看到了一行熟悉的名字:

const char thread_name[] = "Attach Listener";

没错,就是创建了一个新的线程即Attach Listener。该线程在init以后随即创建了一个文件sprintf(fn, ".attach_pid%d", os::current_process_id()); PID文件以及监听了该文件的socket端口,于此同时,attach(pid)做了第三件事:

3、循环等待直至发现由Attach Listener创建的pid文件。

4、检查文件权限,建立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机制响应,从而建立链接会话,然后根据请求返回数据。

 

 

转载于:https://my.oschina.net/u/859433/blog/795089

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值