苍穹定制化开发性能优化指南系列(四)-问题定位之线程堆栈分析原创
金蝶云社区-云社区用户71598104
云社区用户71598104
2人赞赏了该文章 2,136次浏览 未经作者许可,禁止转载编辑于2021年12月07日 14:51:21
summary-icon摘要由AI智能服务提供

本文介绍了线程堆栈分析在定位生产环境问题中的应用,包括线程堆栈的概念、线程状态及死锁现象,并详细讲解了通过苍穹的monitor平台及其他工具(如jdk命令、MAT工具、Arthas)来查看和分析线程堆栈的方法。文章强调了线程堆栈分析在解决系统性能问题、线程安全问题、资源耗尽等方面的有效性,并提供了具体的操作步骤和注意事项。

问题定位之线程堆栈分析

前言

上一篇我们浅谈了在苍穹中如何排查生产环境的问题,在实际使用时,可能使用比较多方法的还是线程的堆栈分析,比如当前系统卡死或者执行很慢的情况,这时候我们想知道代码运行到哪里了,是否哪一段代码有问题导致程序很慢,是否是进入了死循环,或者出现了线程不安全的情况,或者是某些连接数或者打开文件数太多等问题,我们可以通过线程堆栈的分析解决上述的种种情况和疑惑。

我们之前已经介绍了在苍穹的monitor平台如何查看线程堆栈信息,此篇文章我们将会更加详细的讲解monitor平台堆栈信息,java中的线程堆栈以及其他分析线程堆栈的方式。

线程堆栈介绍

在进行线程堆栈分析前,我们先来简单回顾下关于线程堆栈、状态、死锁等相关内容。

2.1线程堆栈

什么是线程堆栈?线程堆栈也称线程调用堆栈,是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某一个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况。线程堆栈的信息主要包含:

1、线程名称,线程id,线程的数量等;

2、线程的运行状态,锁的状态(锁被哪个线程持有,哪个线程在等待锁等);

3、调用堆栈(即函数的调用层次关系):包含完整的类名,所执行的方法,源代码的行号。

在多线程场合下的稳定性问题分析和性能问题分析,线程堆栈分析是最有效的方法,借助堆栈信息可以帮助我们缩小问题范围,找到突破口常用于分析如下类型问题:

1系统无缘无故的cpu过高

2系统挂起,无响应

3系统运行越来越慢

4性能瓶颈(如无法充分利用cpu等)

5线程死锁,死循环等

6由于线程数量太多导致的内存溢出(如无法创建线程等)

2.2线程的状态

线程一共有6种状态:

1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法,该对象变为就绪状态(ready)。就绪状态的线程位于可运行线程池中,等待被线程调度选中以获取CPU使用权,在获得CPU时间片后变为运行中状态(running)

3.  阻塞(BLOCKED):表示线程阻塞于锁。

4.  等待(WAITING): 进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5.  超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6.  终止(TERMINATED):表示该线程已经执行完毕。

实际上JVM 线程栈中,几乎不会出现 NEW,TERMINATED这些状态,其中 Runnable 就是正在运行了,处于WAITING, BLOCKED, TIME_WAITING 的

是不消耗 CPU 的,处于 Runnable 状态的线程,是否消耗 cpu 要看具体的上

下文情况:

(1)如果是纯Java运算代码,则消耗CPU;

(2)如果是网络IO,很少消耗CPU;

以下是状态转化图,可以较为清晰地看到状态转换的场景与条件:

image.png

2.3线程死锁

线程死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

如下图所示:R1 和 R2,都只能被一个进程使用,T1 在使用 R1,同时没有使用完 R1 的情况下,想使用 R2,T2 在使用 R2,同时在没有使用完 R2 的情况下,想使用 R1

 image.png

Monitor平台堆栈

从上一篇指南中我们已经知道了如何在苍穹的monitor中获取对应线程的堆栈信息了,那么在获得了堆栈信息之后,有哪些应该去关注的关键信息以及怎么分析?

image.png

1. 如上图所示,以苍穹中某一线程为例,monitor上该线程堆栈有以下信息:

(1)线程类型:一般可通过类型筛选对应类型的线程,有所有线程(All thread),活动线程(living thread),web请求的线程(web request thread),活动web请求线程(living web request thread),服务线程(service thread),活动服务线程(living service thread);

(2)线程状态的统计:当前线程各状态的数量统计,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING;

(3)具体线程的线程名、traceId、启动时间、运行时间:一般可通过线程名或是traceId搜索找到对应线程;

(4)线程统计详情:事务、数据库、插件、操作服务执行次数等;

(5)堆栈信息:即函数的调用层次关系。

2. 线程统计:在线程统计的信息中,一般需要关注的参数:

(1)DB,DB执行次数比较多的话表示在大量访问数据库,可能存在循环取数或插入大量数据,需要优化为批量查询或分批插入;

(2)RedisSessionlessCache,大量存取缓存也会产生性能问题,需要优化减少查询次数或增加本地线程缓存减少对Redis压力,如果是redis有问题,我们也可以在monitor中的redis监控指标查看其状态和慢查询进一步确认分析

 image.png

(3)如果堆栈并未卡在Redis、DB等IO操作,应该排查代码是否存在死循环;

3. 堆栈信息:monitor显示的堆栈信息是当前线程的调用上下文,即从哪个函数调用到哪个函数(从下往上看),正执行到哪一类的哪一行,借助这些信息,我们一般可以找出有问题的代码行

 image.png

4. 线程锁:需要关注是否出现死锁Deadlock,某线程长时间占有锁- locked id ,等待释放锁-waiting to lock id,锁特征Waiting on condition等待资源,Waiting on monitor entry等待获取监视器等。如果出现死锁,会直接在页面展示相关信息,如下图所示:

 image.png

其他分析方式

我们除了通过monitor获取堆栈信息,也可以使用其他方式直接到容器中手动跟踪线程堆栈来定位问题。比如jdk提供的命令,工具如Arthas等

4.1 jdk命令

1. 获取java进程id:

用法:执行jps命令

 image.png

2. 获取堆栈信息

1)用法:执行jstack pid命令,其中pid为进程id

如下图所示,可以获得该进程下线程的详细堆栈信息

 image.png

(2)如果需要保存堆栈信息,也可以使用jstack $pid >> $file_path/stack.log (路径和文件名自定义)将所有线程的堆栈信息写入到指定的文件,保存到对应的目录下。

3. CPU资源占用高的情况,比如代码出现死循环,我们需要先定位具体哪个线程占用cpu最高

(1)我们可以通过top命令查看各个进程的cpu使用情况,它默认是按cpu使用率由高到低排序的

 image.png

2)通过执行top -Hp pid可以查看该进程下,各个线程的cpu使用情况

注意:堆栈信息中的线程id是16进制表示的,先将上述线程ID转换为16进制并输出,得到16进制值后,可以方便在日志文件中查找对应的线程堆栈信息。

4. 获取堆内存相关信息Heap Dump:一般用于分析内存泄漏

1)手动dump出堆内存相关信息

用法:执行jmap -dump:format=b,file=$file_path/heap.hprof $pid 命令;

注意:其中file_path是文件路径,heap.hprof是文件名称,pid是线程id;

2)发生oom时,自动dump

用法:在JVM参数中设置以下参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof

3jmap命令:使用jmap除了dump堆内存外,还可以通过jmap -heap pid查看GC算法,堆配置参数等,jmap -histo pid 查看类的大小等。

4.2MAT工具

通过手动或者oom时自动dump下来的文件,我们可以结合MAT等工具进行分析,以下为使用MAT工具分析举例:

1. Leak Suspects查找并分析内存泄漏的可能原因

Reports--->Leak Suspects--->Details

 image.png

2. Histogram:直方图,列出内存中的对象,对象的个数及其大小。

1Class Name类名称,java类名

2Objects类的对象的数量,这个对象被创建了多少个

3Shallow Heap一个对象内存的消耗大小,不包含对其他对象的引用

4Retained Heapshallow Heap的总和,即该对象被GC之后所能回收到内存的总和

操作方法:

1)右击类名-->List Objects-->with incoming references-->列出该类的实例;

2)右击Java对象名-->Merge Shortest Paths to GC Roots-->exclude all ...-->找到GCRoot以及原因。

 image.png

3. dominator_tree:列出Heap Dump中处于活跃状态中的最大的几个对象,可以找到占用内存最多的对象进行分析

 image.png

4.3 Arthas工具

1. 简介:Arthas 是Alibaba开源的Java诊断工具,采用命令行交互模式,可以分析类的加载,报错信息,监控JVM的实时运行状态等,我们这里主要关注怎么查看线程堆栈信息。

2. 使用方法和步骤:

(1)下载

执行命令curl -O https://alibaba.github.io/arthas/arthas-boot.jar 或者直接安装使用 as. sh

(2)运行atrhas-boot.jar:执行java -jar arthas-boot.jar pid

(3)thread命令可以看到线程的id、名称、状态、占用cpu这些信息定位哪个线程耗CPU

 image.png

(4)thread pid命令:查看指定线程详细信息

 image.png

(5)thread –b命令:找出阻塞其它线程的线程



下载路径:云之家企业云盘

企业文件->【苍穹平台】共享资料库05->定制化开发标准化工具包->03开发规范


如有疑问或者更好的建议,欢迎各位小伙伴留言或私聊~

图标赞 2
2人点赞
还没有人点赞,快来当第一个点赞的人吧!
图标打赏
0人打赏
还没有人打赏,快来当第一个打赏的人吧!