jstack 命令的基本使用

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

# 最基本的使用
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 上:
shell

"DubboServerHandler-10.64.16.66:20779-thread-510" #631 daemon prio=5 os_prio=0 tid=0x00007fb6f4ce5800 nid=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)
        ...

阻塞在内置锁上:
apache

"qtp302870502-26-acceptor-0@45ff00a-ServerConnector@63475ace{HTTP/1.1}{0.0.0.0:9088}" #26 prio=5 os_prio=0 tid=0x00007f1830d3a800 nid=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=5 os_prio=0 tid=0x00007fc2f6b1f800 nid=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 的输出内容做详细梳理;api

输出内容的结构浏览器

首先仍是要说一下 jstack 输出的内容结构, 就以上方举的第四个线程为例:
如下是第一部份内容, 记录了线程的一些基本信息, 从左到右每一个元素的含义已经以注释标注在元素上方; 其中比较重要的是 nid, 它是 java 线程与操做系统的映射, 在 linux 中它和与其对应的轻量级进程 pid 相同 (须要十六进制与十进制转换), 这将为基于 java 线程的性能诊断带来帮助, 详细请见本文后面段落 #线程性能诊断的辅助脚本;
微信

//|-----线程名------| |-线程建立次序-| |是否守护进程| |---线程优先级---| |-------线程 id-------| |-所映射的linux轻量级进程id-| |-------------线程动做--------------|
  "JFR request timer" #6              daemon        prio=5 os_prio=0  tid=0x00007fc2f6b1f800 nid=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) 或者 TIMED_WAITING (on object monitor), 这种通常与线程动做 in Object.wait() [0x...] 一块儿出现, 而且线程调用栈的最末端调用方法为 at java.lang.Object.wait(Native Method), 以表示 object.wait() 方法的调用;
    另外, WAITING 与 TIMED_WAITING 的区别在因而否设置了超时中断, 即 wait(long timeout) 与 wait() 的区别;

  4. WAITING (parking) 或者 TIMED_WAITING (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 与 TIMED_WAITING 的区别与第三点中提到的缘由一致;

线程的重要调用修饰

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:
=============================
"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:
===================================================
"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 优化做决策依据;

C1 / C2 CompilerThread
虚拟机的 JIT 及时编译器线程:

"C1 CompilerThread2" #10 daemon prio=9 os_prio=0 tid=0x00007feb34114000 nid=0x18b2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #9 daemon prio=9 os_prio=0 tid=0x00007feb34112000 nid=0x18b1 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #8 daemon prio=9 os_prio=0 tid=0x00007feb3410f800 nid=0x18b0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

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

Reference Handler 线程用于将目标对象放入 reference queue:

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f91e007f000 nid=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 线程用于从 reference queue 中取出对象以执行其 finalize 方法:

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f91e0081000 nid=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)

GC 线程
这块对于不一样的 gc 收集器选型有各自不一样的线程状态 (线程数视 cpu 核心数而定);

# Parallel Scavenge
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f91e0021000 nid=0xa7a runnable 
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f91e0023000 nid=0xa7b runnable

# ParNew
"Gang worker#0 (Parallel GC Threads)" os_prio=0 tid=0x00007feb3401e800 nid=0x18a4 runnable 
"Gang worker#1 (Parallel GC Threads)" os_prio=0 tid=0x00007feb34020000 nid=0x18a5 runnable
# CMS
"Concurrent Mark-Sweep GC Thread" os_prio=0 tid=0x00007feb34066800 nid=0x18a8 runnable
# G1
"G1 Main Concurrent Mark GC Thread" os_prio=0 tid=0x00007fc2f4091800 nid=0x1805e runnable

"Gang worker#0 (G1 Parallel Marking Threads)" os_prio=0 tid=0x00007fc2f4093800 nid=0x1805f runnable 
"Gang worker#1 (G1 Parallel Marking Threads)" os_prio=0 tid=0x00007fc2f4095800 nid=0x18060 runnable

"G1 Concurrent Refinement Thread#0" os_prio=0 tid=0x00007fc2f4079000 nid=0x1805d runnable 
"G1 Concurrent Refinement Thread#1" os_prio=0 tid=0x00007fc2f4077000 nid=0x1805c runnable

以上即是 java 进程里一般都会存在的线程;

使用代码做 thread dump

除了使用 jstack 以外, 还有其余一些方法能够对 java 进程做 thread dump, 若是将其封装为 http 接口, 即可以不用登录主机, 直接在浏览器上查询 thread dump 的状况; 
使用 jmx 的 api

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

使用 Thread.getAllStackTraces() 方法

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

线程性能诊断的辅助脚本

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

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

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

         # 将记录下的十进制 pid 转为十六进制
thread_id_0x=`printf "%x" $thread_id`
`echo "obase=16; $thread_id" | bc`
  • 因为 thread dump 中记录的每一个线程的 nid 是与 linux 轻量级进程 pid 一一对应的 (只是十进制与十六进制的区别), 因此即可以拿转换获得的十六进制 thread_id_0x, 去 thread dump 中搜索对应的 nid, 定位问题线程;

下面介绍一个脚本, 其功能是: 按照 cpu 使用率从高到低排序, 打印指定 jvm 进程的前 n 个线程;

#!/bin/sh

default_lines=10
top_head_info_padding_lines=8
default_stack_lines=15

jvm_pid=$1
jvm_user=$2
((thread_stack_lines=${3:-$default_lines}+top_head_info_padding_lines))

threads_top_capture=$(top -b -n1 -H -p $jvm_pid | grep $jvm_user | head -n $thread_stack_lines)
jstack_output=$(echo "$(sudo -i -u $jvm_user jstack $jvm_pid)")
top_output=$(echo "$(echo "$threads_top_capture" | perl -pe 's/\e\[?.*?[\@-~] ?//g' | awk '{gsub(/^ +/,"");print}' | awk '{gsub(/ +|[+-]/," ");print}' | cut -d " " -f 1,9 )\n ")

echo "***********************************************************"
uptime
echo "Analyzing top $top_threads threads"
echo "***********************************************************"

printf %s "$top_output" | while IFS= read line
do
    pid=$(echo $line | cut -d " " -f 1)
    hexapid=$(printf "%x" $pid)
    cpu=$(echo $line | cut -d " " -f 2)
    echo -n $cpu "% [$pid] "
    echo "$jstack_output" | grep "tid.*0x$hexapid " -A $default_stack_lines | sed -n -e '/0x'$hexapid'/,/tid/ p' | head -n -1
done

该脚本有多种版本, 在我司的每台主机上的指定路径下都存放了一个副本; 出于保密协议, 该脚本源码不便于公开, 上方所展现的版本是基于美团点评的技术专家王锐老师在一次 问答分享 中给出的代码所改造的;

原文地址:https://my.oschina.net/u/3825800/blog/4402756

jstack 命令使用经验总结的更多相关文章

  1. jstack命令使用

    概述 jstack可用于导出java运用程序的线程堆栈.其基本使用语法为: jstack [-l] pid -l 选项用于打印锁的额外信息. 使用演示样例 以下这段代码执行之后会出现死锁现象(由于线程 ...

  2. jstack 命令的使用和堆栈分析

    原文:https://www.cnblogs.com/kongzhongqijing/articles/3630264.html 一.介绍 jstack 是 Java 虚拟机自带的一种堆栈跟踪工具.j ...

  3. jstack命令的使用

    文章来源:https://blog.csdn.net/wufaliang003/article/details/80414267 jstack是java虚拟机自带的一种堆栈跟踪工具. jstack用于 ...

  4. 【JVM】jvm至jstack命令

    一.介绍 jstack是java虚拟机自带的一种堆栈跟踪工具.jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项&qu ...

  5. jstack 命令学习笔记

    大部分内容转载自:Java命令学习系列(二)--Jstack jstack - 查看堆栈信息 jstack ( Stack Trace for java ) 命令主要作用就是为了查看堆栈信息.它可以用 ...

  6. jstack命令dump线程信息

    jstack命令dump线程信息 D:\Java\jdk1.8.0_05\bin>jstack.exe 6540 > dump17 6540为java 线程pid: 出来的dump17文件 ...

  7. 给你的Java程序拍个片子吧:jstack命令解析

    前言 如果有一天,你的Java程序长时间停顿,也许是它病了,需要用jstack拍个片子分析分析,才能诊断具体什么病症,是死锁综合征,还是死循环等其他病症,本文我们一起来学习jstack命令~ jsta ...

  8. Java的jstack命令使用详解

    jstack命令简介 jstack(Java Virtual Machine Stack Trace)是JDK提供的一个可以生成Java虚拟机当前时刻的线程快照信息的命令行工具.线程快照一般被称为th ...

  9. Linux jstack命令详解

    jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息. 如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack ...

随机推荐

  1. 【题解】Luogu p3047 [USACO12FEB]附近的牛Nearby Cows 树型dp

    题目描述 Farmer John has noticed that his cows often move between nearby fields. Taking this into accoun ...

  2. NOIP模拟测试4「礼物·通讯·奇袭」

    礼物. 首先见到期望一定要想dp,看到n的范围无脑想状压, 然后我就只想到这了. dp方程式还是比较好想的,但是我依然想不出来 略经思考   颓题解 依然不会,随便写了个式子 i状态中不含j $f[i ...

  3. CVPR2021|一个高效的金字塔切分注意力模块PSA

    ​ 前言: 前面分享了一篇<继SE,CBAM后的一种新的注意力机制Coordinate Attention>,其出发点在于SE只引入了通道注意力,CBAM的空间注意力只考虑了局部区域的信息 ...

  4. 解放生产力,自动化生成vue组件文档

    一.现状 Vue框架在前端开发中应用广泛,当一个多人开发的Vue项目经过长期维护之后往往会沉淀出很多的公共组件,这个时候经常会出现一个人 开发了一个组件而其他维护者或新接手的人却不知道这个组件是做什么 ...

  5. Linux运维网络基础

    1.网络架构的三个层次 核心层: 路由器(网关接口) 实现和外网通讯 冗余能力(主备) 汇聚层: 交换机(三层交换机) 冗余能力 策略控制能力 接入层: 交换机(二层交换机) 终端设备接入网络 2.网 ...

  6. 基于uniapp自定义Navbar+Tabbar组件「兼容H5+小程序+App端Nvue」

    uni-app跨端自定义navbar+tabbar组件|沉浸式导航条|仿咸鱼凸起标签栏 在跨端项目开发中,uniapp是个不错的框架.采用vue.js和小程序语法结构,使得入门开发更容易.拥有非常丰富 ...

  7. 【Azure 应用服务】Azure Function App 执行PowerShell指令[Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt]错误

    问题描述 使用PowerShell脚本执行获取Azure订阅列表的指令(Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt).在本地 ...

  8. 暑假自学java第一天

    今天通过网上的学习资料安装了Java的环境和java的程序开发工具包(JDK) 还安装了eclipse ,英语不太好,所以不太会用这个软件,网上搜了教程,还是出现了问题:unnamed package ...

  9. Springboot:Springboot+mysql5.7搭建服务,超过8小时连接mysql失败

    报错信息 2017-03-12 03:00:02.539 ERROR 9311 --- [nio-9000-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] ...

  10. springCloud学习05之api网关服务zuul过滤器filter

    前面学习了zuul的反向代理.负载均衡.fallback回退.这张学习写过滤器filter,做java web开发的对filter都不陌生,那就是客户端(如浏览器)发起请求的时候,都先经过过滤器fil ...