本文介绍了线程堆栈分析在定位生产环境问题中的应用,包括线程堆栈的概念、线程状态及死锁现象,并详细讲解了通过苍穹的monitor平台及其他工具(如jdk命令、MAT工具、Arthas)来查看和分析线程堆栈的方法。文章强调了线程堆栈分析在解决系统性能问题、线程安全问题、资源耗尽等方面的有效性,并提供了具体的操作步骤和注意事项。
问题定位之线程堆栈分析
1 前言
上一篇我们浅谈了在苍穹中如何排查生产环境的问题,在实际使用时,可能使用比较多方法的还是线程的堆栈分析,比如当前系统卡死或者执行很慢的情况,这时候我们想知道代码运行到哪里了,是否哪一段代码有问题导致程序很慢,是否是进入了死循环,或者出现了线程不安全的情况,或者是某些连接数或者打开文件数太多等问题,我们可以通过线程堆栈的分析解决上述的种种情况和疑惑。
我们之前已经介绍了在苍穹的monitor平台如何查看线程堆栈信息,此篇文章我们将会更加详细的讲解monitor平台堆栈信息,java中的线程堆栈以及其他分析线程堆栈的方式。
2 线程堆栈介绍
在进行线程堆栈分析前,我们先来简单回顾下关于线程堆栈、状态、死锁等相关内容。
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;
以下是状态转化图,可以较为清晰地看到状态转换的场景与条件:
2.3线程死锁
线程死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
如下图所示:R1 和 R2,都只能被一个进程使用,T1 在使用 R1,同时没有使用完 R1 的情况下,想使用 R2,T2 在使用 R2,同时在没有使用完 R2 的情况下,想使用 R1
3 Monitor平台堆栈
从上一篇指南中我们已经知道了如何在苍穹的monitor中获取对应线程的堆栈信息了,那么在获得了堆栈信息之后,有哪些应该去关注的关键信息以及怎么分析?
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监控指标查看其状态和慢查询进一步确认分析
(3)如果堆栈并未卡在Redis、DB等IO操作,应该排查代码是否存在死循环;
3. 堆栈信息:monitor显示的堆栈信息是当前线程的调用上下文,即从哪个函数调用到哪个函数(从下往上看),正执行到哪一类的哪一行,借助这些信息,我们一般可以找出有问题的代码行
4. 线程锁:需要关注是否出现死锁Deadlock,某线程长时间占有锁- locked id ,等待释放锁-waiting to lock id,锁特征Waiting on condition等待资源,Waiting on monitor entry等待获取监视器等。如果出现死锁,会直接在页面展示相关信息,如下图所示:
4 其他分析方式
我们除了通过monitor获取堆栈信息,也可以使用其他方式直接到容器中手动跟踪线程堆栈来定位问题。比如jdk提供的命令,工具如Arthas等
4.1 jdk命令
1. 获取java进程id:
用法:执行jps命令
2. 获取堆栈信息
(1)用法:执行jstack pid命令,其中pid为进程id
如下图所示,可以获得该进程下线程的详细堆栈信息
(2)如果需要保存堆栈信息,也可以使用jstack $pid >> $file_path/stack.log (路径和文件名自定义)将所有线程的堆栈信息写入到指定的文件,保存到对应的目录下。
3. CPU资源占用高的情况,比如代码出现死循环,我们需要先定位具体哪个线程占用cpu最高
(1)我们可以通过top命令查看各个进程的cpu使用情况,它默认是按cpu使用率由高到低排序的
(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
(3)jmap命令:使用jmap除了dump堆内存外,还可以通过jmap -heap pid查看GC算法,堆配置参数等,jmap -histo pid 查看类的大小等。
4.2MAT工具
通过手动或者oom时自动dump下来的文件,我们可以结合MAT等工具进行分析,以下为使用MAT工具分析举例:
1. Leak Suspects:查找并分析内存泄漏的可能原因。
Reports--->Leak Suspects--->Details
2. Histogram:直方图,列出内存中的对象,对象的个数及其大小。
(1)Class Name:类名称,java类名;
(2)Objects:类的对象的数量,这个对象被创建了多少个;
(3)Shallow Heap:一个对象内存的消耗大小,不包含对其他对象的引用;
(4)Retained Heap:是shallow Heap的总和,即该对象被GC之后所能回收到内存的总和。
操作方法:
(1)右击类名-->List Objects-->with incoming references-->列出该类的实例;
(2)右击Java对象名-->Merge Shortest Paths to GC Roots-->exclude all ...-->找到GCRoot以及原因。
3. dominator_tree:列出Heap Dump中处于活跃状态中的最大的几个对象,可以找到占用内存最多的对象进行分析
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
(4)thread pid命令:查看指定线程详细信息
(5)thread –b命令:找出阻塞其它线程的线程
下载路径:云之家企业云盘
企业文件->【苍穹平台】共享资料库05->定制化开发标准化工具包->03开发规范
如有疑问或者更好的建议,欢迎各位小伙伴留言或私聊~
苍穹定制化开发性能优化专题系列(四).pdf(1.21MB)
推荐阅读