常见的CPU飙高和Full GC频繁的原因
- CPU使用率高通常是因为有线程在持续占用CPU资源 ,可能是
死循环
、频繁的GC
、或者代码中的计算密集型操作
。Full GC频繁则通常说明内存管理有问题 ,比如内存泄漏
、对象生命周期过长
,或者JVM参数配置不当导致堆内存不足
,频繁触发Full GC。
快速定位方法
- 应该从监控工具开始,比如检查系统监控(如CPU、内存使用情况) ,然后深入到
JVM层面
的分析,比如堆内存dump、线程分析
等。
CPU飙高的情况
- 首先需要确定是
系统层面
的问题还是Java进程
的问题。使用top命令 查看哪个进程的CPU使用率高 ,然后使用top -Hp [pid]
查看该进程中各个线程的CPU占用情况
。 - 如果是
Java进程
,可以用jstack
获取线程堆栈 ,结合线程ID(转换为十六进制)查找对应的线程堆栈,分析线程在执行什么操作。
CPU飙高问题定位三板斧
1. 快速定位Java进程及线程
-
查找高CPU进程:
bashtop -c # 按CPU排序,找到占用高的Java进程PID
-
查看进程内高CPU线程:
bashtop -Hp [PID] # 显示进程内线程CPU使用,记录线程ID(如12345)
-
将线程ID转换为十六进制:
bashprintf "%x\n" 12345 # 输出0x3039
核心原理 :HotSpot JVM通过os_thread_create
创建本地线程(hotspot/src/os/linux/vm/os_linux.cpp
),JVM线程与OS线程一一对应 。高CPU通常对应RUNNABLE
状态的线程。
2. 分析线程堆栈
-
抓取线程快照:
bashjstack [PID] > jstack.log # 生成线程堆栈文件
-
搜索高CPU线程 :
在
jstack.log
中查找nid=0x3039
的线程堆栈,定位代码位置。常见原因:
- 死循环 :如
while(true)
未合理休眠。 - 密集计算:如正则表达式回溯、复杂算法。
- 频繁GC :垃圾收集线程占用CPU(如
GC task thread
)。
- 死循环 :如
3. 使用Arthas实时诊断
bash
# 启动Arthas并附加到目标进程
java -jar arthas-boot.jar
# 监控高CPU方法
dashboard # 实时查看线程CPU
thread -n 3 # 显示CPU占用Top3线程
trace [ClassName] [methodName] # 追踪方法内部调用耗时
Full GC频繁的问题
- 首先要检查
GC日志
,如果没有开启,需要配置JVM参数
启用GC日志
。然后使用jstat
查看内存各区域的使用情况,比如Eden、Survivor、Old Gen
的使用率,观察是否有内存泄漏的迹象。 - 此外,内存dump分析工具(如MAT) 可以帮助找出哪些对象占用了大量内存,从而定位内存泄漏的位置。
Full GC频繁问题定位
GC日志分析法
bash
# 开启详细GC日志
-Xlog:gc*=debug:file=gc.log
# 查看内存分布
jmap -histo:live <PID> | head -20
1. 检查GC状态
-
查看GC统计:
bashjstat -gcutil [PID] 1000 # 每秒输出一次各区域使用率
关键指标:
O
(Old区使用率):持续接近100%可能内存泄漏。FGC
/FGCT
:Full GC次数及耗时,若频繁增长需警惕。
-
分析GC日志 :
启用GC日志(JVM参数):bash-Xlog:gc*,gc+heap=debug:file=gc.log:time,uptime:filecount=10,filesize=50m
日志关键点:
- Full GC触发原因(如
Allocation Failure
)。 - GC前后堆内存变化(如回收效果差)。
- Full GC触发原因(如
2. 内存Dump分析
-
生成堆转储文件:
bashjmap -dump:live,format=b,file=heap.hprof [PID] # 触发Full GC后dump
-
使用MAT分析:
- 打开
heap.hprof
,查看Dominator Tree
,找到占用内存最大的对象。 - 检查
Leak Suspects
报告,定位疑似内存泄漏点。
常见泄漏场景:
- 静态集合未清理(如
static Map
缓存)。 - 未关闭的资源(数据库连接、文件流)。
- 打开
3. 常见问题及解决
- Young区过小 :频繁对象晋升触发Full GC,调整
-Xmn
增大新生代。 - 内存泄漏:修复代码中未释放的资源或集合累积。
- 大对象分配 :避免在Old区直接分配大对象(调整
-XX:PretenureSizeThreshold
)。
整合排查流程
-
CPU与GC关联分析:
- 若
GC线程
(如G1 Main Marker
)占用CPU高,说明GC压力大。 - 结合
jstat
判断是否因内存不足导致频繁GC。
- 若
-
紧急恢复措施:
- CPU飙高 :通过
kill -3 [PID]
生成线程快照后重启实例。 - Full GC频繁 :扩容实例堆内存(临时调整
-Xmx
),后续优化代码。
- CPU飙高 :通过
工具与命令速查表
工具/命令 | 用途 | 示例 |
---|---|---|
jps |
找到Java进程ID | jps |
top |
定位高CPU进程和线程 | top -Hp [PID] |
jstack |
抓取线程堆栈 | jstack -l [PID] > dump.log |
jstat |
实时监控GC状态 | jstat -gcutil [PID] 1000 |
jmap |
生成堆内存Dump文件 | jmap -dump:format=b [PID] |
Arthas | 动态追踪方法耗时和线程问题 | trace com.example.Service |
MAT (Memory Analyzer) | 分析内存泄漏 | 加载heap.hprof 文件 |
预防与优化建议
-
监控告警:
- 配置Prometheus监控CPU、GC次数、堆内存使用率。
- 设置阈值告警(如Full GC次数每分钟>2次)。
-
定期演练:
- 通过Chaos Engineering模拟高负载,验证系统容错能力。
-
代码规范:
- 避免在循环中创建大对象或调用阻塞IO。
- 使用弱引用(
WeakReference
)管理缓存。
通过上述步骤,可快速定位CPU或GC问题,结合日志和代码分析,针对性优化系统性能。