jstack问题定位分析

本文介绍了如何使用jstack工具分析Java应用程序的线程状态,包括线程的5种状态及其含义,以及如何识别和处理由log4j 1.X版本引发的线程死锁问题、wait挂起线程和连接池导致的等待状态。通过jstack生成的thread dump,可以深入理解线程的行为并优化应用性能。

jstack

jstackjava虚拟机自带的一种堆栈跟踪工具jstack用于打印出给定的java进程IDcore file远程调试服务的Java堆栈信息。

jstack主要用于生成java虚拟机当前时刻的线程快照,线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

top命令

在linux环境下,可以通过top命令查看各个进程的cpu使用情况,默认按cpu使用率排序。

jstack命令

通过top命令定位到cpu占用率较高的线程之后,继续使用jstack pid命令查看当前java进程的堆栈状态。

jstack命令生成的thread dump信息包含了JVM中所有存活的线程,为了分析指定线程,必须找出对应线程的调用栈,应该如何找?

在top命令中,已经获取到了占用cpu资源较高的线程pid,将该pid转成16进制的值,在thread dump中每个线程都有一个nid,找到对应的nid即可;隔段时间再执行一次stack命令获取thread dump,区分两份dump是否有差别。

线程5种状态

新建状态(New) 新创建了一个线程对象。

就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

运行状态(Running) 就绪状态的线程获取了CPU,执行程序代码。

阻塞状态(Blocked) 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

通过thread dump分析线程状态

基于thead dump分析当前各个线程的运行情况,如是否存在死锁、是否存在一个线程长时间持有锁不放等等。

在dump中,线程一般存在如下几种状态:

  • RUNNABLE,线程运行中或I/O等待
  • BLOCKED,线程被阻塞,在等待monitor锁(synchronized关键字)
  • TIMED_WAITING 线程在等待唤醒,但设置了时限
  • WAITING 线程在无限等待唤醒

log4j 1.X版本引发线程blocked死锁问题(synchronized同步锁)

线程处于BLOCK状态。

  • 该线程是blocked在了log4j.Category.callAppenders上,显然可以发现这是个log4j的问题。
  • 解决方法:使用log4j2或者使用slf4j替代直接使用log4j

wait挂起线程

线程1和2都处于WAITING状态

  • 线程1和2都是先locked <0x000000076bf62500>,再waiting on <0x000000076bf62500>,之所以先锁再等同一个对象,是因为wait方法需要先通过synchronized获得该地址对象的monitor;
  • waiting on <0x000000076bf62500>说明线程执行了wait方法之后,释放了monitor,进入到"Wait Set"队列,等待其它线程执行地址为0x000000076bf62500对象的notify方法,并唤醒自己 

连接池

线程处于WAITING状态

  • 多执行几次jstack可以发现大约有部分的线程处于waitting状态,该状态表明该线程正在执行obj.wait()方法,放弃了 Monitor,进入 “Wait Set”队列,为什么阻塞住呢,继续往下看堆栈信息,可以看到该线程正在做borrowobject操作,可以大概看到这里是一个数据库连接池的相关操作,可以适当调整数据库连接池大小

 

```markdown 代码概述 该文件是一个Java核心转储(javacore)日志文件,通常由IBM JRE或OpenJ9虚拟机生成,类似于标准的`jstack`输出。它记录了JVM在某一时刻的所有线程状态、调用栈、堆内存分配、CPU使用情况等信息,常用于诊断性能问题、死锁、线程阻塞等问题。 代码解析 1. **基本信息**: - 时间戳:2025年11月12日 10:27:07(本地时间),UTC+8。 - JVM版本:IBM Semeru Runtime Open Edition 8.0.462.0(基于OpenJ9),运行在Linux amd64系统上。 - 启动时间:2025/11/11 13:23:39,已运行约21小时。 2. **线程分析重点**: - 存在多个处于 `CW`(Waiting on Condition Wait)状态的用户线程,如 `"Thread-53"`, `"Thread-72"`, `"Thread-82"` 等。 - 这些线程均执行到以下代码路径: ``` com.jsfj.ects.infrastructure.immsg.RedisDelayQueue.listenDelayLoop(RedisDelayQueue.java:71) → Thread.sleep(...) ``` 表明这些是自定义的消息监听线程,在循环中通过 `Thread.sleep()` 实现延时轮询。 - 多个线程长时间处于 `sleep` 状态(Java层面调用 `Thread.sleep()`),这是正常行为,但如果导致消息延迟或响应慢,则可能需要优化轮询间隔。 3. **潜在关注点**: - 有若干线程处于 `R`(Runnable)状态,例如 `"XNIO-1 task-14"` 正在读取 Socket 输入流: ``` java.net.SocketInputStream.socketRead0(Native Method) ``` 表示应用正在进行网络I/O操作,可能是HTTP请求处理或客户端通信。 - 部分后台线程(如 Nacos、Quartz、Logback、XNIO)正常工作,无异常堆栈。 - 没有发现死锁(可通过搜索 `Deadlock` 判断,本文件未出现)。 - 所有线程均未显示 `BLOCKED` 状态,说明当前不存在线程竞争阻塞。 4. **资源与调度**: - 使用的是 `sun.misc.Launcher$AppClassLoader` 加载类,未使用自定义类加载器。 - 线程优先级均为默认值(prio=5),策略未知(policy: UNKNOWN),但属于常规设置。 - CPU使用方面,部分消费线程累计消耗超过5秒CPU时间,表明其活跃度较高。 5. **结论性观察**: - 该JVM实例存在大量用于监听Redis延迟队列的独立线程(如 Thread-53, Thread-72...),每条都在调用 `Thread.sleep()` 控制频率。 - 若此类线程过多(可统计数量),可能导致线程资源浪费或上下文切换开销增大,建议改造成线程池 + 定时任务模式(如 `ScheduledExecutorService`)以提升效率。 知识点(列出该代码中遇到的知识点) 1. **Java线程状态模型**:包括RUNNABLE、CONDITION_WAIT、PARKING等JVM内部状态,对应Java线程的wait/sleep/block等行为。 2. **Javacore日志结构**:IBM/OpenJ9生成的核心转储日志,包含线程快照、堆信息、寄存器状态等,用于故障排查。 3. **Thread.sleep()机制**:使当前线程暂停执行指定时间,释放CPU但不释放锁,常用于简单轮询控制。 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值