jstack是如何获取threaddump的?

欢迎访问陈同学博客原文

JDK提供了许多命令行工具用于监视JVM,让我们可以了解其异常堆栈、GC日志、threaddump、heapdump等信息。一时好奇,想看看jstack是如何实现的?

jstack使用小例子

先以一个小场景简单示范下 jstack 的使用。

场景:Java应用持续占用很高CPU,需要排查一下。

模拟:造个场景简单模拟下,没什么实际意义,仅作演示。我启动了100个线程持续访问 我的博客,博客部署在Ubuntu 16.04上,是一个简单的Spring Boot应用,以jar包直接运行的。

top 命令查下系统运行情况,进程31951占用CPU 80.6%。

jps -l 确认一下,31951就是博客的进程ID,或 cat /proc/31951/cmdline 看下进程的启用命令。

root@iZ94dcq8q6jZ:~# jps -l
28416 sun.tools.jps.Jps
31951 blog.jar

top -Hp 31951 以线程模式查看下进程31951的所有线程情况

假设想看下第二个线程31998的情况,31998是操作系统的线程ID,先转成16进制。

printf '%x' 31998 #值为7cfe

获取该线程的信息(匹配7cf3后取20行差不多)

jstack 31951 | grep 7cfe -A 20

其中部分数据如下:

"Tomcat JDBC Pool Cleaner[11483240:1532362388783]" #31 daemon prio=5 os_prio=0 tid=0x0a29dc00 nid=0x7cfe in Object.wait() [0xa2a69000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at java.util.TimerThread.mainLoop(Timer.java:552)
    - locked <0xaadc5a60> (a java.util.TaskQueue)
    at java.util.TimerThread.run(Timer.java:505)

注意:nid=0x7cfe中的nid指native id,是OS中线程ID,对应上面31998线程的16进制值7cfe;tid为Java中线程的ID。

至于如何利用jstack的数据分析线程情况,可以看看 如何使用jstack分析线程状态jstack

jstack实现原理

本部分不深入源码,浅尝即止,只是想看看工具是如何与JVM通讯以获取各项诊断数据的。更深入的源码分析,可以看看 聊聊jstack的工作原理

先以一段简单代码打印threaddump,和stack命令效果一样,下面的类基本来自 tools.jar

@Test
public void jstack() throws Exception {
    RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
    String pid = runtimeMXBean.getName().split("@")[0];

    VirtualMachine virtualMachine = VirtualMachine.attach(pid);
    HotSpotVirtualMachine hotSpotVirtualMachine = (HotSpotVirtualMachine) virtualMachine;

    InputStream inputStream = hotSpotVirtualMachine.remoteDataDump(new String[]{});
    String threadDump = IOUtils.toString(inputStream, "utf8"); // IOUtils from commons-io
    System.out.println(threadDump);
    virtualMachine.detach();
}

打印的部分数据如下:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):

"Attach Listener" #10 daemon prio=9 os_prio=31 tid=0x00007f816293c800 nid=0x5b0f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007f8162827000 nid=0x5303 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #8 daemon prio=9 os_prio=31 tid=0x00007f8164834800 nid=0x5103 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

核心的hotSpotVirtualMachine.remoteDataDump()代码:

public InputStream remoteDataDump(Object... var1) throws IOException {
    return this.executeCommand("threaddump", var1);
}

private InputStream executeCommand(String var1, Object... var2) throws IOException {
    try {
        return this.execute(var1, var2);
    } catch (AgentLoadException var4) {
        throw new InternalError("Should not get here", var4);
    }
}

很多命令都是通过 executeCommand 来实现的,例如:datadump、threaddump、dumpheap、inspectheap、jcmd等,而最终的execute()在Mac机器上是由 BsdVirtualMachine 类来完成。

为了便于阅读,源码我有较大删减,看看execute()中的原英文注释即可。

/**
 * Execute the given command in the target VM.
 */
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
    // did we detach?
    String p;
    synchronized (this) {
        if (this.path == null) {
            throw new IOException("Detached from target VM");
        }
        p = this.path;
    }

    // create UNIX socket
    int s = socket();

    // connect to target VM
    connect(s, p);

    IOException ioe = null;

    // connected - write request
    // <ver> <cmd> <args...>
    writeString(s, PROTOCOL_VERSION);
    writeString(s, cmd);

    for (int i=0; i<3; i++) {
        if (i < args.length && args[i] != null) {
            writeString(s, (String)args[i]);
        } else {
            writeString(s, "");
        }
    }

    // Create an input stream to read reply
    SocketInputStream sis = new SocketInputStream(s);

    // Read the command completion status
    int completionStatus = readInt(sis);

    // Return the input stream so that the command output can be read
    return sis;
}

代码是最好的手册,通过代码可以知道:jstack等命令会与jvm进程建立socket连接,发送对应的指令(jstack发送了threaddump指令),然后再读取返回的数据

小结

所谓"工欲善其事,必先利其器",在工作中根据各种场景熟练玩转各类常用工具,能极大的提高效率。


欢迎关注陈同学的公众号,一起学习,一起成长

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

恶意程序分析利器PowerShellArsenal

简介 PowerShellArsenal是一个PowerShell模块,它的功能是帮助逆向工程师来分析.NET恶意软件,PowerShellArsenal的功能...

2589
来自专栏IT可乐

mybatis源码解读(三)——数据源的配置

1353
来自专栏潇涧技术专栏

Head First Android Testing 1

最近想写一个自己的库项目,以后开发都基于这个库项目来开发,于是乎,为了保证库项目中的代码功能没有问题,简单学了一些Android测试的内容,对于没有搞过测试的我...

862
来自专栏用户2442861的专栏

web.xml配置详解

1、web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是web应用的配置文件。

3671
来自专栏深度学习计算机视觉

java RMI学习笔记RMI(Remote Method)Java RMI 威力强大Java远程消息交换协议JRMP使用RMI优点RMI网络模型网络模型RMI的工作原理RMI远程调用步骤:编码实现j

RMI(Remote Method) Invocation):远程方法调用,即在RPC的基础上有向前迈进了一步,提供分布式对象间的通讯。允许运行在一个java虚...

2985
来自专栏技术墨客

Spring核心——Bean的依赖注入 原

在设计模式与IoC这篇文章中,介绍了Spring基础的三大支柱的两项内容——IoC、Bean。本篇将继续围绕着Bean的创建时的注入方式来介绍Spring的核心...

911
来自专栏Jack-Cui

Linux应用层系统时间写入RTC时钟的方法

Linux内核版本:linux-3.0.35 开发板:i.MX6S MY-IMX6-EK200 系统:Ubuntu12 前言:之前写过一篇关于如...

2150
来自专栏JavaQ

高并发编程-CyclicBarrier深入解析

CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(也可以叫同步点),即相互等待的线程都完成调用await方法...

7803
来自专栏恰童鞋骚年

.NET单元测试的艺术-2.核心技术

开篇:上一篇我们学习基本的单元测试基础知识和入门实例。但是,如果我们要测试的方法依赖于一个外部资源,如文件系统、数据库、Web服务或者其他难以控制的东西,那又该...

1172
来自专栏Java学习123

一个简单的AXIS远程调用Web Service示例

4647

扫码关注云+社区

领取腾讯云代金券