热门标签 | 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。


推荐阅读
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 本文深入解析了JDK 8中HashMap的源代码,重点探讨了put方法的工作机制及其内部参数的设定原理。HashMap允许键和值为null,但键为null的情况只能出现一次,因为null键在内部通过索引0进行存储。文章详细分析了capacity(容量)、size(大小)、loadFactor(加载因子)以及红黑树转换阈值的设定原则,帮助读者更好地理解HashMap的高效实现和性能优化策略。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在iOS开发中,基于HTTPS协议的安全网络请求实现至关重要。HTTPS(全称:HyperText Transfer Protocol over Secure Socket Layer)是一种旨在提供安全通信的HTTP扩展,通过SSL/TLS加密技术确保数据传输的安全性和隐私性。本文将详细介绍如何在iOS应用中实现安全的HTTPS网络请求,包括证书验证、SSL握手过程以及常见安全问题的解决方法。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 将JavaScript文件嵌入HTML文档是Web开发中的基本操作。常见的方法是通过在HTML文件中使用``标签来引用外部的.js文件。这种方法不仅保持了代码的整洁性,还便于管理和维护。此外,还可以利用模块化脚本和异步加载技术进一步提升页面性能。 ... [详细]
  • 本课程详细解析了Spring AOP的核心概念及其增强机制,涵盖前置增强、后置增强和环绕增强等类型。通过具体示例,深入探讨了如何在实际开发中有效运用这些增强技术,以提升代码的模块化和可维护性。此外,还介绍了Spring AOP在异常处理和性能监控等场景中的应用,帮助开发者更好地理解和掌握这一强大工具。 ... [详细]
  • 掌握 esrally 三步骤:高效执行 Elasticsearch 性能测试任务
    自从上次发布 esrally 教程已近两个月,期间不断有用户咨询使用过程中遇到的各种问题,尤其是由于测试数据托管在海外 AWS 上,导致下载速度极慢。为此,本文将详细介绍如何通过三个关键步骤高效执行 Elasticsearch 性能测试任务,帮助用户解决常见问题并提升测试效率。 ... [详细]
  • 微服务应用性能如何?APM监控工具来告诉你
    当微服务系统越来越庞大,各个服务间的调用关系也变得越来越复杂,需要一个工具来帮忙理清请求调用的服务链路。之前使用的是Sleuth+Zipkin的解决方案,最近发现应 ... [详细]
  • Logstash安装配置
    阅读此文请先阅读上文:[大数据]-Elasticsearch5.3.1IK分词,同义词联想搜索设置,前面介绍了ES,Kiba ... [详细]
  • ElasticSearch版本:elasticsearch-7.3.0环境准备:curl-HContent-Type:applicationjso ... [详细]
  • 工具系列 | 分布式日志管理graylog 实战
    Graylog是一个开源的日志聚合、分析、审计、展现和预警工具。功能上和ELK类似,但又比ELK要简单,依靠着更加简洁,高效, ... [详细]
  • Elasticsearch简单使用系列安装
    2019独角兽企业重金招聘Python工程师标准1.elasticsearch支持的操作系统和JVM版本https:www.elastic.cosupportmatrix2. ... [详细]
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社区 版权所有