当 Linux 下 Java 程序出现 CPU / 内存高占用时,核心排查思路

当 Linux 下 Java 程序出现 CPU / 内存高占用时,核心排查思路是:先定位进程→再定位线程 / 内存问题→最后结合系统 / 业务逻辑溯源,以下是分步落地的排查方法,尤其针对你提到的 “定时脚本导致” 场景补充重点:
一、基础定位:找到高占用的 Java 进程 / 线程
1. 定位高 CPU / 内存的 Java 进程
用top或ps快速筛选目标进程,这是第一步:
bash
运行
# 方法1:top交互式查看(按P排序CPU,按M排序内存,按c显示完整命令)
top -c

# 方法2:直接筛选Java进程(重点看%CPU、%MEM、PID列)
ps aux | grep java | grep -v grep
# 输出示例:USER  PID  %CPU %MEM  VSZ   RSS  TTY  STAT  START   TIME  COMMAND
#          root 1234  90.0 30.0 819200 49152 ?    Sl    10:00  1:20  java -jar app.jar
记录关键信息:PID(进程 ID)、%CPU/%MEM(占用率)、COMMAND(启动命令,确认是否是目标程序)。
2. 定位进程内高 CPU 的线程(针对 CPU 高场景)
Java 进程的 CPU 高通常是某个 / 某些线程导致,需进一步定位:
bash
运行
# 1. 查看该Java进程下的所有线程(-Hp:显示线程,按P排序CPU)
top -Hp <Java进程PID>
# 记录高CPU的线程ID(TID列,十进制)

# 2. 将线程ID转为16进制(jstack日志中线程ID是16进制)
printf "%x\n" <高CPU线程TID>
# 示例:输入1234 → 输出4d2(注意小写,jstack中是0x4d2格式)
二、Java 层面深度分析:找到线程 / 内存的具体问题
1. 线程栈分析(排查 CPU 高:死循环 / 无限计算 / 阻塞等)
用jstack抓取线程栈,分析高 CPU 线程在执行什么逻辑:
bash
运行
# 抓取目标Java进程的线程栈,保存到文件
jstack <Java进程PID> > jstack_$(date +%Y%m%d).log

# 查找16进制线程ID对应的线程信息(比如线程ID转成的4d2)
grep -A 20 "0x4d2" jstack_*.log
关键解读 jstack 日志:
看线程状态:RUNNABLE(运行中,大概率是耗 CPU 的线程)、BLOCKED(阻塞)、WAITING(等待);
看栈帧:找到线程执行的代码行(比如com.xxx.service.Task.run()),定位到具体业务方法;
常见问题:死循环(比如while(true)没退出条件)、频繁计算(比如大数据量循环处理)、锁竞争(大量线程 BLOCKED)。
2. 内存 & GC 分析(排查内存高场景)
内存高可能是堆配置不足、内存泄漏、GC 频繁导致,用jstat+jmap分析:
bash
运行
# 1. 实时监控GC状态(每1秒输出1次,共输出10次)
jstat -gc <Java进程PID> 1000 10
# 输出解读:
# S0C/S1C:幸存区大小 | S0U/S1U:幸存区已用 | EC/EDU:伊甸园区 | OC/OU:老年代
# YGC/YGT:Young GC次数/耗时 | FGC/FGT:Full GC次数/耗时
# 重点:FGC频繁(比如每秒几次)、OU接近OC → 老年代满,内存泄漏/堆配置小

# 2. 导出堆内存快照(分析内存泄漏)
jmap -dump:format=b,file=heap_dump_$(date +%Y%m%d).hprof <Java进程PID>
# 用MAT(Memory Analyzer Tool)打开hprof文件,分析:
# - 大对象(比如超大List/Map)、重复对象;
# - 未释放的资源(比如数据库连接、线程池、定时任务实例)。
3. 易用工具:Arthas(阿里开源,简化排查)
如果觉得jstack/jmap上手难,推荐用 Arthas(一键安装,交互式排查):
bash
运行
# 安装并启动Arthas(需和Java进程同用户)
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 选择目标Java进程(输入序号回车)

# 常用命令:
dashboard          # 实时查看CPU/内存/线程/GC状态(核心)
thread             # 查看所有线程,标注高CPU线程
thread -n 5        # 显示CPU占用前5的线程(直接看栈帧)
jad com.xxx.service.Task  # 反编译可疑类,看代码逻辑
watch com.xxx.service.Task run '{params, returnObj}' -x 2  # 监控方法入参/返回值
三、系统层面排查(针对 “定时脚本导致” 的场景)
你的问题根源是定时脚本重复启动程序,这类场景需重点排查以下点:
1. 检查定时任务配置
Linux 定时任务主要在这些位置,逐一排查:
bash
运行
# 1. 查看当前用户的crontab
crontab -l
# 2. 查看系统级crontab
cat /etc/crontab
# 3. 查看crontab.d目录下的定时任务
ls /etc/cron.d/
cat /etc/cron.d/*
# 4. 查看anacron(非实时定时任务)
cat /etc/anacrontab
重点看:是否有重复的定时任务(比如同一脚本被配置多次)、任务执行频率是否过高(比如每秒执行)、脚本是否没做 “进程存活检查”。
2. 检查 Java 进程是否重复启动
定时脚本如果没判断 “程序是否已运行”,会导致多实例堆积,抢占 CPU / 内存:
bash
运行
# 统计Java进程数量(正常应该只有1个,若多个则是重复启动)
ps -ef | grep java | grep -v grep | wc -l

# 查看进程启动时间(确认是否是定时任务触发的新实例)
ps -eo pid,lstart,cmd | grep java | grep -v grep
3. 检查定时脚本逻辑
脚本的常见问题:
没判断进程是否存活就启动:比如脚本里直接java -jar app.jar,没有ps -ef | grep app.jar || java -jar app.jar;
脚本执行后没退出:比如脚本里的命令阻塞,导致定时任务堆积;
脚本输出大量日志:日志写入磁盘会导致 CPU/IO 高,排查:
bash
运行
# 查看脚本输出的日志文件大小
du -sh /path/to/log/*.log
# 查看日志写入速度
tail -f /path/to/log/app.log  # 看是否有刷屏的日志
4. 检查系统资源限制
定时任务启动过多进程,可能触发系统资源限制,导致异常:
bash
运行
# 查看当前用户的资源限制(线程数、文件句柄等)
ulimit -a
# 重点看:max user processes(最大进程数)、open files(最大文件句柄)
# 查看系统级限制
cat /etc/security/limits.conf
四、常见高 CPU / 内存原因总结(结合定时脚本场景)
进程重复启动:定时脚本没做进程存活检查,导致多个 Java 实例同时运行,抢占 CPU / 内存;
定时任务执行频率过高:比如脚本每分钟执行一次,但程序启动 / 执行需要 5 分钟,导致任务堆积;
代码逻辑问题:定时任务触发的业务逻辑有死循环、大数据量计算、内存泄漏(比如定时任务每次执行都创建新线程 / 对象,不释放);
GC 异常:堆内存配置过小,定时任务每次执行产生大量临时对象,导致 Full GC 频繁(CPU 高);
脚本本身问题:脚本执行 shell 命令耗 CPU(比如循环执行ps/grep),而非 Java 程序本身。
五、排查流程总结(可直接套用)
快速定位:top找高占用 Java 进程 → top -Hp找高 CPU 线程 → jstack/Arthas 分析线程逻辑;
内存排查:jstat看 GC 状态 → jmap导出堆快照 → MAT 分析内存泄漏;
系统溯源:排查定时任务配置 → 检查进程是否重复启动 → 审核脚本逻辑;
验证修复:停掉重复进程 / 调整脚本 → 观察 CPU / 内存是否恢复 → 长期监控(比如nmon/prometheus)。
关键避坑点
不要只看 Java 进程,忽略脚本本身(比如脚本里的 shell 循环也会耗 CPU);
定时脚本一定要加 “进程锁”(比如用flock)或 “存活检查”,避免重复启动;
排查时保留日志(jstack/GC/ 堆快照),便于后续复盘;
若线上环境不能直接导出堆快照,优先用 Arthas 的heapdump命令(支持远程 / 低侵入)。

 

posted @ 2025-12-18 16:18  全琪俊  阅读(8)  评论(0)    收藏  举报