JVM核心技术-25FastThread相关的工具介绍:欲穷千里目,更上一层楼

25 FastThread 相关的工具介绍:欲穷千里目,更上一层楼

FastThread 简介

在前面的章节里,我们知道了可以打印出来 JVM 的所有线程信息,然后进行分析。然而所有的线程信息都很长,看起来又差不多,每次去看都让人头大。

所以,每当我去分析线程都在想,要是有工具能帮我把一般情况汇总,并自动帮我分析分析 JVM 线程情况就好了。这里要介绍的 FastThread 就是这么一款工具。

FastThread 是一款线程转储(Thread Dump)分析工具,官网地址为:http://fastthread.io/

这款工具由 tier1app 公司 开发和支持,这家公司现在主要提供 3 款 JVM 分析工具,除了 FastThread 还有:

  • GCEasy,访问地址:https://gceasy.io/,详情请参考前面的文章 [《GC 日志解读与分析(番外篇可视化工具)》]。
  • HeapHero,官网地址:https://heaphero.io/,顾名思义,这是一款 Heap Dump 分析工具。

FastThread 工具可用来分析和定位问题,功能特征包括: 通用线程转储分析,FastThread 是一款通用的线程转储分析工具,可以通过 JVM 导出的线程转储,来进行根本原因排查分析(RCA,root cause analysis)。 提供在线分析功能,因为线程转储一般不会太大,所以只需上传我们导出的线程转储文件即可快速查看分析报告,而不需要在本地计算机下载和安装。使用非常方便。 提供直观的线程分析视图,通过仪表盘等形式的图形展示,使用起来既简单又容易理解。并对各种线程状态进行分类,比如阻塞、运行、定时等待、等待,以及重复的堆栈跟踪。通过这款工具,可以快速方便地解决可扩展性、性能问题和可用性问题。 支持 REST 方式的 API 接口调用,FastThread 是业界第一款支持 API 方式的线程转储分析工具。通过 API 接口,我们就可以通过脚本或者程序实现自动化分析,适用于进行批量的操作。 支持核心转储分析(Core Dump Analysis),Java 核心转储包括很多信息,但格式非常难以理解和解析。FastThread 可以分析 Java 核心转储文件,并以图形方式提供精确的信息。 分析 hs_err_pid 文件,进程崩溃(crashes)或致命错误(fatal error)会导致JVM异常终止。这时候 JVM 会自动生成 hs_err_pid 文件。这个文件中包含大量的信息,可以用 FastThread 来帮助我们进行分析。

顺便说一句,JVM 的线程转储不只是 Java 语言有,其他语言也是支持的,例如 Scala、Jython、JRuby 等等。

通过 FastThread 官方网站在线进行线程堆栈分析是"免费"的,下面我们通过示例程序来演示这款工具的使用。 示例程序与线程 Dump

基于前面《JVM 的线程堆栈数据分析》章节中的示例代码,我们简单修改一下,用来模拟死锁和线程等待的状态。

示例程序如下:

package demo.jvm0207;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class DeadLockSample2 {
    public static void main(String[] args) throws Exception {
        DeadLockTask deadLockTask = new DeadLockTask();
        // 多线程模拟死锁
        new Thread(deadLockTask).start();
        new Thread(deadLockTask).start();
        // 等待状态
        Thread wt = new WaitedThread();
        wt.start();
        // 当前线程等待另一个线程来汇合
        wt.join();
    }
    private static class WaitedThread extends Thread {
        @Override
        public void run() {
            synchronized (DeadLockSample2.class) {
                try {
                    DeadLockSample2.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 简单的死锁; 分别锁2个对象
    private static class DeadLockTask implements Runnable {
        private Object lockA = new Object();
        private Object lockB = new Object();
        private AtomicBoolean flag = new AtomicBoolean(false);
        public void run() {
            try {
                if (flag.compareAndSet(false, true)) {
                    synchronized (lockA) {
                        TimeUnit.SECONDS.sleep(2);
                        synchronized (lockB) {
                            System.out.println("死锁内部代码");
                        }
                    }
                    synchronized (lockB) {
                        TimeUnit.SECONDS.sleep(2);
                        synchronized (lockA) {
                            System.out.println("死锁内部代码");
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
    }
}

我们启动程序,会发现系统卡住不动。

然后我们可以用各种工具来探测和检查线程状态,如果有不了解的同学,可以参考前面的 《[JVM 的线程堆栈数据分析]》章节。

线程转储快照(Thread Dump)可用来辅助诊断 CPU 高负载、死锁、内存异常、系统响应时间长等问题。

所以我们需要先获取对应的 Thread Dump 文件:

 查看本地 JVM 进程信息
jps -v
  直接打印线程快照
jstack -l 51399
  将线程快照信息保存到文件
jstack -l 51399 > 51399.thread.dump.txt

jstack 工具得到的线程转储信息大致如下所示:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.162-b12 mixed mode):
"Thread-2" 15 prio=5 os_prio=31 tid=0x00007fb3ee805000 nid=0x5a03 in Object.wait() [0x000070000475d000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x000000076abee388> (a java.lang.Class for demo.jvm0207.DeadLockSample2)
 at java.lang.Object.wait(Object.java:502)
 at demo.jvm0207.DeadLockSample2$WaitedThread.run(DeadLockSample2.java:25)
 - locked <0x000000076abee388> (a java.lang.Class for demo.jvm0207.DeadLockSample2)
   Locked ownable synchronizers:
 - None
"Thread-1" 14 prio=5 os_prio=31 tid=0x00007fb3ed05d800 nid=0x5903 waiting for monitor entry [0x000070000465a000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at demo.jvm0207.DeadLockSample2$DeadLockTask.run(DeadLockSample2.java:52)
 - waiting to lock <0x000000076abf7338> (a java.lang.Object)
 - locked <0x000000076abf7348> (a java.lang.Object)
 at java.lang.Thread.run(Thread.java:748)
   Locked ownable synchronizers:
 - None
"Thread-0" 13 prio=5 os_prio=31 tid=0x00007fb3ef8c1000 nid=0xa703 waiting for monitor entry [0x0000700004557000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at demo.jvm0207.DeadLockSample2$DeadLockTask.run(DeadLockSample2.java:45)
 - waiting to lock <0x000000076abf7348> (a java.lang.Object)
 - locked <0x000000076abf7338> (a java.lang.Object)
 at java.lang.Thread.run(Thread.java:748)
   Locked ownable synchronizers:
 - None
"main" 1 prio=5 os_prio=31 tid=0x00007fb3ee006000 nid=0x2603 in Object.wait() [0x0000700002f15000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x000000076abf7cf8> (a demo.jvm0207.DeadLockSample2$WaitedThread)
 at java.lang.Thread.join(Thread.java:1252)
 - locked <0x000000076abf7cf8> (a demo.jvm0207.DeadLockSample2$WaitedThread)
 at java.lang.Thread.join(Thread.java:1326)
 at demo.jvm0207.DeadLockSample2.main(DeadLockSample2.java:17)
   Locked ownable synchronizers:
 - None
JNI global references: 1358

Found one Java-level deadlock:
------------------------
"Thread-1":
  waiting to lock monitor 0x00007fb3ee01f698 (object 0x000000076abf7338,a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fb3ee01f7f8 (object 0x000000076abf7348,a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
------------------------
"Thread-1":
 at demo.jvm0207.DeadLockSample2$DeadLockTask.run(DeadLockSample2.java:52)
 - waiting to lock <0x000000076abf7338> (a java.lang.Object)
 - locked <0x000000076abf7348> (a java.lang.Object)
 at java.lang.Thread.run(Thread.java:748)
"Thread-0":
 at demo.jvm0207.DeadLockSample2$DeadLockTask.run(DeadLockSample2.java:45)
 - waiting to lock <0x000000076abf7348> (a java.lang.Object)
 - locked <0x000000076abf7338> (a java.lang.Object)
 at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.

工具自动找到了死锁,另外几个处于等待状态的线程也标识了出来。当然,上面省略了其他线程的信息,例如:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.162-b12 mixed mode):
"Thread-2" 15 ... in Object.wait()
   java.lang.Thread.State: WAITING (on object monitor)
"Thread-1" 14 ... waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
"Thread-0" 13 ... waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
"Service Thread" 12 ... daemon prio=9 ... runnable
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" 10 daemon ... waiting on condition
   java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" 4 daemon ... runnable
   java.lang.Thread.State: RUNNABLE
"Finalizer" 3 daemon ... in Object.wait()
   java.lang.Thread.State: WAITING (on object monitor)
"Reference Handler" 2 daemon ... in Object.wait()
   java.lang.Thread.State: WAITING (on object monitor)
"main" 1 ... in Object.wait()
   java.lang.Thread.State: WAITING (on object monitor)
"VM Thread" ... runnable
"GC task thread0 (ParallelGC)" ... runnable
"GC task thread1 (ParallelGC)" ... runnable
"GC task thread2 (ParallelGC)" ... runnable
"GC task thread3 (ParallelGC)" ... runnable
"GC task thread4 (ParallelGC)" ... runnable
"GC task thread5 (ParallelGC)" ... runnable
"GC task thread6 (ParallelGC)" ... runnable
"GC task thread7 (ParallelGC)" ... runnable
"VM Periodic Task Thread" ... waiting on condition

获取到了线程快照信息之后,下面我们来看看怎么使用 FastThread 分析工具。 FastThread 使用示例

打开官网首页:http://fastthread.io/文件上传方式*

img

选择文件并上传,然后鼠标点击"分析”(Analyze)按钮即可。 上传文本方式*

img

两种方式步骤都差不多,选择 RAW 方式上传文本字符串,然后点击分析按钮。 分析结果页面*

等待片刻,自动跳转到分析结果页面。

6843295.png

这里可以看到基本信息,以及右边的一些链接: 分享报告,可以很方便地把报告结果发送给其他小伙伴。 线程数汇总*

把页面往下拉,可以看到线程数量汇总报告。

6864312.png

从这个报告中可以很直观地看到,线程总数为 26,其中 19 个运行状态线程,5 个等待状态的线程,2 个阻塞状态线程。

右边还给了一个饼图,展示各种状态所占的比例。 线程组分析*

接着是将线程按照名称自动分组。

6898070.png

这里就看到线程命名的好处了吧!如果我们的线程池统一命名,那么相关资源池的使用情况就很直观。

所以在代码里使用线程池的时候,统一添加线程名称就是一个好的习惯!

守护线程分析*

接下来是守护线程分析:

6923926.png

这里可以看到守护线程与前台线程的统计信息。 死锁情况检测*

当然,也少不了死锁分析:

6948610.png

可以看到,各个工具得出的死锁检测结果都差不多。并不难分析,其中给出了线程名称,以及方法调用栈信息,等待的是哪个锁。 线程调用栈情况*

以及线程调用情况:

7008839.png

后面是这些线程的详情:

7058206.png

这块信息只是将相关的方法调用栈展示出来。 热点方法统计*

热点方法是一个需要注意的重点,调用的越多,说明这一块可能是系统的性能瓶颈。

7104053.png

这里展示了此次快照中正在执行的方法。如果只看热点方法抽样的话,更精确的工具是 JDK 内置的 hprof。

但如果有很多方法阻塞或等待,则线程快照中展示的热点方法位置可以快速确定问题出现的代码行。 CPU 消耗信息*

img

这里的提示信息不太明显,但给出了一些学习资源,这些资源请参考本文末尾给出的博客链接地址。 GC 线程信息*

img

这里看到 GC 线程数是 8 个,这个值跟具体的 CPU 内核数量相差不大就算是正常的。 GC 线程数如果太多或者太少,会造成很多问题,我们在后面的章节中通过案例进行讲解。 线程栈深度*

7277060.png

这里都小于10,说明堆栈都不深。 复杂死锁检测*

接下来是复杂死锁检测和 Finalizer 线程的信息。

7295147.png

简单死锁是指两个线程之间互相死等资源锁。那么什么复杂死锁呢? 这个问题留给同学们自己搜索。 火焰图*

7336167.png

火焰图挺有趣,将所有线程调用栈汇总到一张图片中。 调用栈树*

如果我们把所有的调用栈合并到一起,整体来看呢?

7358293.png

树形结构在有些时候也很有用,比如大量线程都在执行类似的调用栈路径时。

以上这些信息,都有助于我们去分析和排查 JVM 问题,而图形工具相对于命令行工具的好处是直观、方便、快速,帮我们省去过滤一些不必要的干扰信息的时间。 参考链接

  • [8 个抓取 Java Thread Dumps 的方式]
  • [Thread Dump 选项]
  • [FastThread 官方博客]

文章列表

更多推荐

更多
  • Pharo敏捷人工智能-第一部分:神经网络
    Apache CN

  • Pharo敏捷人工智能-第二部分:遗传算法
    Apache CN

  • Pharo敏捷人工智能-# 第三部分:神经进化 第三部分:神经进化
    Apache CN

  • Azure数据工程指南-二十四、数据治理的权限 创建 azure 预览帐户,探索 azure 预览,探索词汇表,浏览资产,以编程方式使用预览,摘要,管理凭证和访问,创建扫描, 许多组织需要建立数据治理流程、标准和方法,并且已经能够使用内部 SQL Server 工具(如 Master
    Apache CN

  • Azure数据工程指南-二十二、Synapse 分析工作区 创建 Synapse 分析工作区,使用 Spark 探索样本数据,用 SQL 查询数据,用 SQL 创建外部表,摘要, 微软 Azure 数据平台的众多新增功能已经围绕许多类似的产品及其在现代 Azure 数据平台中的用途产生了兴奋和困
    Apache CN

  • Azure数据工程指南-二十三、数据块中的机器学习 创建 MLflow 实验,安装 MLflow 库,创建笔记本,选择性测井,自动记录,摘要, 寻求利用机器学习(ML)和人工智能能力的组织和开发人员花费大量时间构建 ML 模型,并寻求一种方法来简化他们的机器学习开发生命周期,以跟踪实验,
    Apache CN

  • Azure数据工程指南-二十一、将 Apache Spark 的 GraphFrame API 用于图形分析 安装 JAR 库,加载新数据表,将数据加载到数据块笔记本中,用顶点和边构建一个图,查询图表,寻找有图案的图案,用 PageRank 发现重要性,探索入度和出度度量,摘要,进行广度优先搜索,查找连接的组件, 图形技术使用户能够以图形的形式
    Apache CN

  • Azure数据工程指南-20 二十、部署 SQL 数据库先决条件,创建 Visual Studio SQL 数据库项目,安装 Visual Studio GitHub 扩展,导入 AdventureWorks 数据库,连接到 GitHub Repo 源代码控制,将
    Apache CN

  • Azure数据工程指南-十九、部署数据工厂更改 先决条件,创建 DevOps 持续集成构建管道,创建 DevOps 持续部署发布渠道,验证部署的数据工厂资源,摘要,Azure PowerShell 任务停止触发器,ARM 模板部署任务,Azure PowerShell 任务启动触发器
    Apache CN

  • Azure数据工程指南-十八、用于 Cosmos DB 的 Azure Synapse 链接 创建一个 Azure Cosmos DB 帐户,启用 Azure Synapse 链接,创建一个 Cosmos DB 容器和数据库,将数据导入 Azure Cosmos DB,在 Azure Synapse Analytics 中创建
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多