热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

jstack命令使用经验总结和线程性能诊断脚本

编辑:业余草来源:https:www.xttblog.com?p4946分享一下,jstack命令使用经验总结jstack在命令使用上十分

编辑:业余草
来源:https://www.xttblog.com/?p=4946

分享一下,jstack 命令使用经验总结

jstack 在命令使用上十分简洁, 然而其输出的内容却十分丰富, 信息量足, 值得深入分析; 以往对于 jstack 产生的 thread dump, 我很少字斟句酌得分析过每一部分细节, 针对 jstack 的性能诊断也没有一个模式化的总结; 今天这篇文章我就来详细整理一下与 jstack 相关的内容;

jstack 命令的基本使用

jstack 在命令使用上十分简洁, 其信息量与复杂度主要体现在 thread dump 内容的分析上;

# 最基本的使用
sudo -u xxx jstack {vmid}
# 从 core dump 中提取 thread dump
sudo -u xxx jstack core_file_path
# 除了基本输出外, 额外展示 AbstractOwnableSynchronizer 锁的占有信息
# 可能会消耗较长时间
sudo -u xxx jstack -l {vmid}

jstack 输出内容结构分析

首先展示几段 thread dump 的典型例子: 正在 RUNNING 中的线程:

"elasticsearch[datanode-39][[xxx_index_v4][9]: Lucene Merge Thread #2403]" #45061 daemon prio=5 os_prio=0 tid=0x00007fb968213800 nid=0x249ca runnable [0x00007fb6843c2000]java.lang.Thread.State: RUNNABLE...at org.elasticsearch.index.engine.ElasticsearchConcurrentMergeScheduler.doMerge(ElasticsearchConcurrentMergeScheduler.java:94)at org.apache.lucene.index.ConcurrentMergeScheduler$MergeThread.run(ConcurrentMergeScheduler.java:626)

阻塞在 java.util.concurrent.locks.Condition 上:

"DubboServerHandler-10.64.16.66:20779-thread-510" #631 daemon prio&#61;5 os_prio&#61;0 tid&#61;0x00007fb6f4ce5800 nid&#61;0x1743 waiting on condition [0x00007fb68ed2f000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000e2978ef0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)...

阻塞在内置锁上:

"qtp302870502-26-acceptor-0&#64;45ff00a-ServerConnector&#64;63475ace{HTTP/1.1}{0.0.0.0:9088}" #26 prio&#61;5 os_prio&#61;0 tid&#61;0x00007f1830d3a800 nid&#61;0xdf64 waiting for monitor entry [0x00007f16b5ef9000]java.lang.Thread.State: BLOCKED (on object monitor)at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:234)- waiting to lock <0x00000000c07549f8> (a java.lang.Object)at org.eclipse.jetty.server.ServerConnector.accept(ServerConnector.java:377)...at java.lang.Thread.run(Thread.java:745)

"JFR request timer" #6 daemon prio&#61;5 os_prio&#61;0 tid&#61;0x00007fc2f6b1f800 nid&#61;0x18070 in Object.wait() [0x00007fb9aa96b000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00007fba6b50ea38> (a java.util.TaskQueue)at java.lang.Object.wait(Object.java:502)at java.util.TimerThread.mainLoop(Timer.java:526)- locked <0x00007fba6b50ea38> (a java.util.TaskQueue)at java.util.TimerThread.run(Timer.java:505)

以上展示了四个线程的 jstack dump, 有 running 状态, 也有阻塞状态, 覆盖面广, 具有典型性; 下面来对 jstack 的输出内容作详细梳理。

输出内容的结构

首先还是要说一下 jstack 输出的内容结构, 就以上方举的第四个线程为例: 以下是第一部分内容, 记录了线程的一些基本信息, 从左到右每个元素的含义已经以注释标注在元素上方; 其中比较重要的是 nid, 它是 java 线程与操作系统的映射, 在 linux 中它和与其对应的轻量级进程 pid 相同 (需要十六进制与十进制转换), 这将为基于 java 线程的性能诊断带来帮助, 如需帮助&#xff0c;可以加我Wx&#xff1a;codedq&#xff0c;免费获得“线程性能诊断的辅助脚本”和 Shell 视频教程。

//|-----线程名------| |-线程创建次序-| |是否守护进程| |---线程优先级---| |-------线程 id-------| |-所映射的linux轻量级进程id-| |-------------线程动作--------------|"JFR request timer" #6 daemon prio&#61;5 os_prio&#61;0 tid&#61;0x00007fc2f6b1f800 nid&#61;0x18070 in Object.wait() [0x00007fb9aa96b000]

以下是第二部分内容, 表示线程当前的状态。

java.lang.Thread.State: WAITING (on object monitor)

以下是第三部分内容, 主要记录了线程的调用栈; 其中比较重要的是一些关键调用上的 动作修饰, 这些为线程死锁问题的排查提供了依据。

at java.lang.Object.wait(Native Method)
- waiting on <0x00007fba6b50ea38> (a java.util.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at java.util.TimerThread.mainLoop(Timer.java:526)
- locked <0x00007fba6b50ea38> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)

线程的动作

线程动作的记录在每个 thread dump 的第一行末尾, 一般情况下可分为如下几类:

  1. runnable, 表示线程在参与 cpu 资源的竞争, 可能在被调度运行也可能在就绪等待;

  2. sleeping, 表示调用了 Thread.sleep(), 线程进入休眠;

  3. waiting for monitor entry [0x...], 表示线程在试图获取内置锁, 进入了等待区 Entry Set, 方括号内的地址表示线程等待的资源地址;

  4. in Object.wait() [0x...], 表示线程调用了 object.wait(), 放弃了内置锁, 进入了等待区 Wait Set, 等待被唤醒, 方括号内的地址表示线程放弃的资源地址;

  5. waiting on condition [0x...], 表示线程被阻塞原语所阻塞, 方括号内的地址表示线程等待的资源地址; 这种和 jvm 的内置锁体系没有关系, 它是 jdk5 之后的 java.util.concurrent 包下的锁机制。

线程的状态

线程的状态记录在每个 thread dump 的第二行, 并以 java.lang.Thread.State 开头, 一般情况下可分为如下几类:

  1. RUNNABLE, 这种一般与线程动作 runnable 一起出现

  2. BLOCKED (on object monitor), 这种一般与线程动作 waiting for monitor entry 一起出现, 不过在其线程调用栈最末端并没有一个固定的方法, 因为 synchronized 关键字可以修饰各种方法或者同步块

  3. WAITING (on object monitor) 或者 TIMEDWAITING (on object monitor), 这种一般与线程动作 in Object.wait() [0x...] 一起出现, 并且线程调用栈的最末端调用方法为 at java.lang.Object.wait(Native Method), 以表示 object.wait() 方法的调用; 另外, WAITING 与 TIMEDWAITING 的区别在于是否设置了超时中断, 即 wait(long timeout) 与 wait() 的区别;

  4. WAITING (parking) 或者 TIMEDWAITING (parking), 这种一般与线程动作 waiting on condition [0x...] 一起出现, 并且线程调用栈的最末端调用方法一般为 at sun.misc.Unsafe.park(Native Method); Unsafe.park 使用的是线程阻塞原语, 主要在 java.util.concurrent.locks.AbstractQueuedSynchronizer 类中被使用到, 很多基于 AQS 构建的同步工具, 如 ReentrantLock, Condition, CountDownLatch, Semaphore 等都会诱发线程进入该状态; 另外, WAITING 与 TIMEDWAITING 的区别与第三点中提到的原因一致;

线程的重要调用修饰

thread dump 的第三部分线程调用栈中, 一般会把与锁相关的资源使用状态以附加的形式作重点修饰, 这与线程的动作及状态有着密切的联系, 一般情况下可分为如下几类:

  1. locked <0x...>, 表示其成功获取了内置锁, 成为了 owner;

  2. parking to wait for <0x...>, 表示其被阻塞原语所阻塞, 通常与线程动作 waiting on condition 一起出现;

  3. waiting to lock <0x...>, 表示其在 Entry Set 中等待某个内置锁, 通常与线程动作 waiting for monitor entry 一起出现;

  4. waiting on <0x...>, 表示其在 Wait Set 中等待被唤醒, 通常与线程动作 in Object.wait() [0x...] 一起出现;

另外, waiting on 调用修饰往往与 locked 调用修饰一同出现, 如之前列举的第四个 thread dump:

at java.lang.Object.wait(Native Method)- waiting on <0x00007fba6b50ea38> (a java.util.TaskQueue)at java.lang.Object.wait(Object.java:502)at java.util.TimerThread.mainLoop(Timer.java:526)- locked <0x00007fba6b50ea38> (a java.util.TaskQueue)at java.util.TimerThread.run(Timer.java:505)

这是因为该线程之前获得过该内置锁, 现在因为 object.wait() 又将其放弃了, 所以在调用栈中会出现先后两个调用修饰。

死锁检测的展示

在 jdk5 之前, Doug Lea 大神还没有发布 java.util.concurrent 包, 这个时候提及的锁, 就仅限于 jvm 监视器内置锁; 此时如果进程内有死锁发生, jstack 将会把死锁检测信息打印出来:

Found one Java-level deadlock:
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;
"Thread-xxx":waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object),which is held by "Thread-yyy"
"Thread-yyy":waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object),which is held by "Thread-xxx"
Java stack information for the threads listed above:
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;
"Thread-xxx":...
"Thread-yyy":...
Found 1 deadlock.

然而后来 Doug Lea 发布了 java.util.concurrent 包, 当谈及 java 的锁, 除了内置锁之外还有了基于 AbstractOwnableSynchronizer 的各种形式; 由于是新事物, 彼时 jdk5 的 jstack 没有及时提供对以 AQS 构建的同步工具的死锁检测功能, 直到 jdk6 才完善了相关支持;

常见 java 进程的 jstack dump 特征

首先, 不管是什么类型的 java 应用, 有一些通用的线程是都会存在的:VM Thread 与 VM Periodic Task Thread虚拟机线程, 属于 native thread, 凌驾与其他用户线程之上; VM Periodic Task Thread 通常用于虚拟机作 sampling/profiling, 收集系统运行信息, 为 JIT 优化作决策依据;

主线程 main通常 main 线程是 jvm 创建的 1 号用户线程, 有了 main 之后才有了后来的其他用户线程;

Reference Handler 线程与 Finalizer 线程这两个线程用于虚拟机处理 override 了 Object.finalize() 方法的实例, 对实例回收前作最后的判决。

"Reference Handler" #2 daemon prio&#61;10 os_prio&#61;0 tid&#61;0x00007f91e007f000 nid&#61;0xa80 in Object.wait() [0x...]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)- locked <0x00000000c0495140> (a java.lang.ref.Reference$Lock)

"Finalizer" #3 daemon prio&#61;8 os_prio&#61;0 tid&#61;0x00007f91e0081000 nid&#61;0xa81 in Object.wait() [0x...]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)- locked <0x00000000c008db88> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

使用代码作 thread dump

除了使用 jstack 之外, 还有其他一些方法可以对 java 进程作 thread dump, 如果将其封装为 http 接口, 便可以不用登陆主机, 直接在浏览器上查询 thread dump 的情况;使用 jmx 的 api

public void threadDump() {ThreadMXBean threadMxBean &#61; ManagementFactory.getThreadMXBean();for (ThreadInfo threadInfo : threadMxBean.dumpAllThreads(true, true)) {// deal with threadInfo.toString()}
}

使用 Thread.getAllStackTraces() 方法

public void threadDump() {for (Map.Entry stackTrace : Thread.getAllStackTraces().entrySet()) {Thread thread &#61; (Thread) stackTrace.getKey();StackTraceElement[] stack &#61; (StackTraceElement[]) stackTrace.getValue();if (thread.equals(Thread.currentThread())) {continue;}// deal with threadfor (StackTraceElement stackTraceElement : stack) {// deal with stackTraceElement}}
}

线程性能诊断的辅助脚本

操作步骤和很多教程都是一样&#xff0c;只不过&#xff0c;我把它简化成了一个脚本。我在简单重复一下它的主要步骤。

使用 jstack 还有一个重要的功能就是分析热点线程: 找出占用 cpu 资源最高的线程; 首先我先介绍一下手工敲命令分析的方法:

* 使用 top 命令找出 cpu 使用率高的 thread id:*

# -p pid: 只显示指定进程的信息
# -H: 展示线程的详细信息
top -H -p {pid}
# 使用 P 快捷键按 cpu 使用率排序, 并记录排序靠前的若干 pid (轻量级进程 id)

作进制转换:

# 将记录下的十进制 pid 转为十六进制
thread_id_0x&#61;&#96;printf "%x" $thread_id&#96;
&#96;echo "obase&#61;16; $thread_id" | bc&#96;

由于 thread dump 中记录的每个线程的 nid 是与 linux 轻量级进程 pid 一一对应的 (只是十进制与十六进制的区别), 所以便可以拿转换得到的十六进制 threadid0x, 去 thread dump 中搜索对应的 nid, 定位问题线程。

#!/bin/sh
default_lines&#61;10
top_head_info_padding_lines&#61;8
default_stack_lines&#61;15
jvm_pid&#61;$1
jvm_user&#61;$2
((thread_stack_lines&#61;${3:-$default_lines}&#43;top_head_info_padding_lines))
threads_top_capture&#61;$(top -b -n1 -H -p $jvm_pid | grep $jvm_user | head -n $thread_stack_lines)
jstack_output&#61;$(echo "$(sudo -i -u $jvm_user jstack $jvm_pid)")
top_output&#61;$(echo "$(echo "$threads_top_capture" | perl -pe &#39;s/\e\[?.*?[\&#64;-~] ?//g&#39; | awk &#39;{gsub(/^ &#43;/,"");print}&#39; | awk &#39;{gsub(/ &#43;|[&#43;-]/," ");print}&#39; | cut -d " " -f 1,9 )\n ")
echo "***********************************************************"
uptime
echo "Analyzing top $top_threads threads"
echo "***********************************************************"
printf %s "$top_output" | while IFS&#61; read line
dopid&#61;$(echo $line | cut -d " " -f 1)hexapid&#61;$(printf "%x" $pid)cpu&#61;$(echo $line | cut -d " " -f 2)echo -n $cpu "% [$pid] "echo "$jstack_output" | grep "tid.*0x$hexapid " -A $default_stack_lines | sed -n -e &#39;/0x&#39;$hexapid&#39;/,/tid/ p&#39; | head -n -1
done

该脚本的主要功能是: 按照 cpu 使用率从高到低排序, 打印指定 jvm 进程的前 n 个线程。

不懂 shell 的&#xff0c;可以后台回复“shell”获取视频教程。或加我WX&#xff1a;codedq。

thread dump 可视化分析工具

dump 可视化工具比较多&#xff0c;这里推荐两个我常用的。gceasy.io 和 fastthread.io。


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • 这个问题困扰了我两天,卸载Dr.COM客户端(我们学校上网要装这个客户端登陆服务器,以后只能在网页里输入用户名和密码了),问题解决了。问题的现象:在实验室机台式机上安装openfire和sp ... [详细]
author-avatar
QK丫头419QJ
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有