排查CPU占用过高的一次经历

本文详细记录了在Linux服务器上排查定时任务故障的过程,包括使用top命令监控CPU使用率,利用jstack和jmap分析Java进程内存泄漏,最终通过代码优化解决了因内存不足引发的GC频繁导致的任务执行失败问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux服务器上部署着一个工程,此工程是定时任务的集合,几乎承载着所有的定时任务,最近一段时间线上业务总出现莫名奇妙的数据不对的问题等等,查看了一下原因 是因为有些定时任务一直在TIME_WAITING,没有执行导致的。

 

以下为在linux上分析的过程:

1.使用top命令 查看cpu使用情况(实际情况是达到了85%,这是后期截的图)

2.PID为2423的java程序占用cpu比较大,之后具体看哪个线程占用cpu最多

  命令:top -p 2423 -H

 

可以看出线程2438占用cpu最高!

3.此时我们需要查看线程2423是什么工程,需要借助jdk自带的jstack工具(注意jstack查看出的日志线程号都是16进制的)

转16进制:(分享一个个人的小技巧,使用浏览器自带的js完成)

 

4.进入jdk的bin目录下 执行以下命令

命令:./jstack 2423|grep 0x986

5.是个垃圾回收的进程,查看下垃圾回收日志

jstat查看进程内存状况

命令: jstat -gcutil 2423 2000 10

发现FGC特别大 E也几乎满了,继续分析需要dump出日志在分析.

 

6.使用jmap命令导出heapdump文件,然后拿到本地使用jvisualvm.exe分析或者MAT工具(Memory Analyzer Tool)分析(我使用的是eclipse的MAT插件去分析的)。

命令: jmap [option] vmid 
jmap -dump:format=b,file=dump.bin 2423

 

7.将dump日志 倒到本地 用MAT去分析

 

8.安装MAT插件

help->Install New Software

网址:  

http://download.eclipse.org/mat/1.7/update-site/

安装完成,导致dump文件即可,如果你出现内存不够的情况,修改eclipse.ini文件, 改为-Xmx2048m即可解决。

 

9.导入dump文件分析

发现内存占用比较大,具体点Domiantor Tree查看是什么占用这么大内存

继续跟进查看具体的类,查到具体的类,定位到代码最后发现是代码层次的问题,内存中存放大量不需要存放的值,修改代码得以解决!

 

问题分析:

        内存不足,导致java程序 疯狂的垃圾回收GC ,GC占用cpu资源没有释放,导致其他的定时任务无法正常运行!!!

<think>我们正在讨论Java中锁未释放是否会导致CPU使用率过的问题。根据之前的引用和知识,锁未释放通常会导致线程阻塞,但阻塞状态(BLOCKED)的线程不会消耗CPU资源,而是会等待锁。然而,锁未释放可能间接导致CPU,例如: 1. 当多个线程竞争同一个锁时,未获得锁的线程会进入BLOCKED状态,不消耗CPU。 2. 但是,如果持有锁的线程长时间不释放锁(例如,因为死循环或长时间操作),那么其他等待锁的线程会阻塞,而如果这些阻塞的线程很多,可能会导致系统吞吐量下降,进而可能引起其他问题(如任务堆积,触发更多的线程创建,从而增加CPU负担)。 3. 更直接的情况是,如果持有锁的线程在执行一个死循环,那么该线程会一直处于RUNNABLE状态,并且持续消耗CPU资源。 因此,锁未释放本身不会直接导致CPU(因为阻塞的线程不消耗CPU),但锁未释放的原因可能是由于持有锁的线程在执行CPU操作(如死循环)而长时间不释放锁,或者锁竞争激烈导致大量线程在锁外做其他CPU操作(如自旋锁)而消耗CPU。 具体分析如下: 1. **锁未释放与线程状态**: - 在Java中,当一个线程因为请求一个锁而阻塞时(即进入synchronized块或使用Lock对象的lock()方法时锁不可用),该线程会进入`BLOCKED`状态(对于synchronized)或`WAITING`/`TIMED_WAITING`状态(对于LockSupport.park(),如ReentrantLock)。在阻塞状态下,线程不会消耗CPU资源。 - 但是,如果线程在等待锁的过程中采用了“自旋”的方式(比如自旋锁),那么它会持续检查锁的状态,此时线程处于`RUNNABLE`状态,会消耗CPU。在Java中,synchronized锁在竞争时可能会经历自旋阶段(自旋优化),但自旋时间有限,不会无限自旋。而显式锁(如ReentrantLock)可以通过`lock.lock()`(非公平锁尝试自旋一次)或者`tryLock`方法指定自旋时间。 2. **锁未释放的间接影响**: - 如果锁被长时间占用,导致其他线程阻塞,系统可能会创建更多线程来处理任务(如果使用了线程池且任务队列满了,可能会创建新线程,或者有外部请求不断进入),而新线程也可能竞争锁,导致更多线程阻塞。同时,如果线程池中大量线程被阻塞,那么可用的工作线程减少,系统吞吐量下降,可能导致任务积压,进而触发更多的线程创建(如果线程池配置允许),最终导致CPU(因为线程切换和任务处理)。 - 另外,如果持有锁的线程在执行一个死循环,那么该线程会持续占用CPU,导致CPU。 3. **如何排查锁未释放导致的CPU问题**: - 使用`jstack`命令获取线程转储,查看线程状态。如果发现大量线程处于`BLOCKED`状态(等待锁),那么可能是锁竞争激烈。同时,查看持有锁的线程在做什么。如果持有锁的线程处于`RUNNABLE`状态,并且长时间停留在某个方法上,那么可能是该方法执行时间过长(比如死循环)。 - 使用`jstack`可以查看到锁的持有者和等待者。例如,在jstack输出中,等待锁的线程会显示: ``` "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001e345800 nid=0x3d44 waiting for monitor entry [0x000000001f0cf000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.DeadlockExample$Resource.method2(DeadlockExample.java:30) - waiting to lock <0x000000076b15e6d0> (a com.example.DeadlockExample$Resource) at com.example.DeadlockExample$2.run(DeadlockExample.java:59) ``` 而持有锁的线程会显示: ``` "Thread-0" #11 prio=5 os_prio=0 tid=0x000000001e345000 nid=0x3b9c runnable [0x000000001efce000] java.lang.Thread.State: RUNNABLE at com.example.DeadlockExample$Resource.method1(DeadlockExample.java:20) - locked <0x000000076b15e6d0> (a com.example.DeadlockExample$Resource) at com.example.DeadlockExample$1.run(DeadlockExample.java:44) ``` - 如果持有锁的线程一直处于RUNNABLE状态,并且长时间不释放锁,那么就需要检查该线程的代码,看是否有死循环或长时间操作。 4. **解决方案**: - 优化锁的范围:尽量减少锁的持有时间,只锁必要的代码块。 - 避免在锁中执行耗时操作(如IO操作、复杂计算等)。 - 使用更级的并发工具:如`java.util.concurrent`包中的`ReentrantLock`,它支持尝试获取锁(`tryLock`)和带超时的获取锁,避免无限期等待。 - 如果发现死循环,需要修复代码逻辑,添加退出条件。 结论:锁未释放本身不会直接导致CPU,因为阻塞的线程不消耗CPU。但是,锁未释放的原因(如持有锁的线程陷入死循环)会直接导致CPU,或者锁竞争激烈导致大量线程自旋(消耗CPU)或系统创建过多线程(线程切换消耗CPU)从而间接导致CPU。 因此,在排查CPU问题时,如果发现大量线程阻塞,应该关注持有锁的线程在执行什么,以及锁竞争是否激烈。 下面我们通过一个示例代码来说明:</think>### Java 锁未释放是否会导致 CPU 使用率过? 是的,**Java 中锁未释放可能导致 CPU 使用率过**,但这是通过间接机制实现的。锁未释放本身不会直接消耗 CPU,但会引发连锁反应导致 CPU 飙升。以下是详细分析: --- #### 一、锁未释放导致 CPU的机制 1. **线程阻塞与资源饥饿** - 当线程 A 持有锁但未释放时,其他请求该锁的线程会进入 `BLOCKED` 状态 - 阻塞线程不消耗 CPU,但会导致任务积压 - 系统可能创建新线程处理积压任务,增加线程切换开销 - 线程切换的上下文切换(Context Switching)消耗大量 CPU 资源 $$ \text{CPU 消耗} \propto \text{线程数} \times \text{切换频率} $$ 2. **自旋锁消耗** - 某些锁实现(如 `synchronized` 的轻量级锁)会使用自旋等待 - 自旋的线程处于 `RUNNABLE` 状态,持续检查锁状态 ```java while (!lockAvailable) { // 空循环消耗 CPU } ``` - 自旋操作会使 CPU 核心保持 100% 利用率[^1][^2] 3. **资源死锁的连锁反应** - 锁未释放可能引发死锁(如 A 持有锁 L1 等 L2,B 持有 L2 等 L1) - 死锁导致线程池所有工作线程阻塞 - 新请求触发线程池扩容(如 Tomcat 线程池) - 大量新线程加剧 CPU 竞争 --- #### 二、排查锁未释放的步骤 1. **定位 CPU 线程** ```bash top -H -p <java_pid> # 查看线程级 CPU 占用 printf "%x\n" <tid> # 将十进制线程 ID 转十六进制 ``` 2. **分析线程栈** ```bash jstack <java_pid> > thread_dump.txt ``` - 搜索 `BLOCKED` 状态线程: ```log "Thread-1" #12 prio=5 os_prio=0 tid=0x00007f... nid=0x3d44 waiting for monitor entry java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Service.process(Service.java:42) - waiting to lock <0x000000076b15e6d0> # 被阻塞的锁地址 ``` - 查找锁持有者: ```log "Thread-0" #11 prio=5 os_prio=0 tid=0x00007f... nid=0x3b9c runnable at com.example.Service.process(Service.java:35) - locked <0x000000076b15e6d0> # 持有锁的线程 ``` 3. **使用诊断工具** - **Arthas**:`thread -b` 一键检测死锁 - **VisualVM**:监控线程状态分布 - **JFR**(Java Flight Recorder):记录锁竞争事件 ```bash jcmd <pid> JFR.start duration=60s filename=lock.jfr ``` --- #### 三、解决方案与预防措施 1. **代码修复** - 添加锁超时机制(避免永久阻塞): ```java ReentrantLock lock = new ReentrantLock(); if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试获取锁,最多等待1秒 try { // 临界区代码 } finally { lock.unlock(); } } else { // 处理超时逻辑 } ``` - 缩小同步范围:只锁必要代码块 - 用 `ConcurrentHashMap` 替代 `synchronized` 集合[^2] 2. **线程池调优** - 设置合理的线程池参数: ```java new ThreadPoolExecutor( corePoolSize, maxPoolSize, 60s, // 空闲线程存活时间 new LinkedBlockingQueue(100) // 有界队列 ); ``` - 监控队列堆积:`ThreadPoolExecutor.getQueue().size()` 3. **锁监控与告警** - 集成 APM 工具(如 SkyWalking)监控: - 锁等待时间 - 线程阻塞计数 - 设置报警规则:`锁等待时间 > 200ms` 触发告警 --- #### 四、典型案例分析 某电商系统曾因订单服务未释放锁导致 CPU 100%[^1]: 1. 问题现象:CPU 持续 100%,服务超时 2. 排查过程: - `jstack` 发现 20+ 线程阻塞在 `OrderService.lock` - 持有锁的线程在执行耗时 SQL 查询 3. 解决方案: - 将 `synchronized` 改为 `ReentrantLock.tryLock(500ms)` - SQL 查询添加索引优化 - 添加熔断机制(Hystrix) 4. 结果:CPU 从 100% 降至 30%,99 线延迟从 5s 降至 200ms > **关键结论**:锁未释放通过线程阻塞→资源饥饿→线程膨胀→上下文切换的链条间接导致 CPU 飙升。需结合线程分析和代码优化解决[^1][^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值