Java线程CPU飙高、僵尸进程和磁盘满的排查技巧

 更新时间:2026年02月11日 08:46:58   作者:予枫的编程笔记  
线上故障就像埋在系统里的定时炸弹——CPU突然飙高导致服务卡顿、海量日志找不到异常、磁盘满了直接宕机,每一种都能让程序员头皮发麻,所以今天整理了4类高频线上故障的排查套路,附实操命令,需要的朋友可以参考下

线上故障就像埋在系统里的“定时炸弹”——CPU突然飙高导致服务卡顿、海量日志找不到异常、磁盘满了直接宕机,每一种都能让程序员头皮发麻。
不用瞎试错,不用靠经验瞎猜,今天整理了4类高频线上故障的排查套路,附实操命令,从定位到解决一步到位,收藏起来,下次故障直接抄作业!

一、CPU飙高:快速定位到具体Java线程(实操拉满)

线上服务突然卡顿、接口响应超时,大概率是CPU飙高惹的祸。很多新手遇到这种情况,只会用top命令看一眼CPU占用,却不知道怎么定位到具体的代码线程,最后只能瞎折腾。

分享一套极简排查流程,从定位进程到找到异常线程,全程只需几行命令,新手也能快速上手👇

1. 第一步:定位CPU占用最高的进程

先用top命令查看系统CPU使用情况,重点关注%CPU列,找到占用率最高的进程(PID):

top
  • 操作技巧:输入top后,按「P」键(大写),可以按CPU占用率从高到低排序,一眼就能找到“罪魁祸首”进程。
  • 关键说明:如果是Java服务,进程名一般是java,记住对应的PID(比如12345),后续用得上。

2. 第二步:定位进程中CPU占用最高的线程

找到异常进程后,用top -H -p 命令,定位该进程下占用CPU最高的线程(TID):

top -H -p 12345  # 12345替换为第一步找到的PID
  • 核心作用:-H参数会将进程拆分到线程级别,按「P」键排序后,就能找到占用CPU最高的线程ID(TID,比如12346)。

3. 第三步:将线程ID转换为16进制(关键一步)

因为Java的jstack日志中,线程ID是16进制的,所以需要将第二步找到的TID(十进制)转换为16进制:

printf "%x\n" 12346  # 12346替换为第二步找到的TID
  • 示例:假设转换后得到的16进制线程ID是303a(注意小写,jstack日志中是小写)。

4. 第四步:用jstack打印日志,定位异常线程

最后用jstack命令打印该进程的线程日志,并用grep过滤出目标线程,就能找到异常代码的堆栈信息:

jstack 12345 | grep 303a -A 20  # 12345是进程PID,303a是16进制线程ID,-A 20表示显示后续20行
  • 关键解读:日志中会显示该线程正在执行的代码行、方法名,顺着堆栈信息找下去,就能快速定位到导致CPU飙高的具体代码(比如死循环、频繁GC等)。

小提示:排查完成后,记得点赞收藏,下次遇到CPU飙高,直接按这个流程来,不用再翻资料!

二、日志分析:海量日志中快速定位异常(grep组合拳)

线上服务报错,日志文件动辄几百M、几个G,直接打开查找异常信息,不仅耗时,还容易看漏。

分享3个最实用的grep组合命令,帮你快速从海量日志中筛选出异常信息,效率翻倍👇

1. 基础用法:筛选包含指定关键词的日志

最常用的场景:查找包含“Error”“Exception”等异常关键词的日志,快速定位报错位置:

# 筛选包含Error的日志,显示行号
grep -n "Error" app.log

# 筛选包含Exception的日志,忽略大小写(比如Exception、exception都能匹配)
grep -i "Exception" app.log

2. 进阶用法:筛选指定时间段的日志

很多时候,我们知道故障发生的大致时间段,只需筛选该时间段内的日志,缩小查找范围:

# 假设日志格式是 2026-02-10 14:30:00 错误信息,筛选14:30-14:40之间的Error日志
grep "2026-02-10 14:3[0-4]" app.log | grep "Error"
  • 灵活调整:根据自己项目的日志格式,修改时间匹配规则(比如yyyy-MM-dd HH:mm:ss、MM-dd HH:mm等)。

3. 高阶用法:筛选异常日志并输出到文件

如果异常日志较多,可将筛选结果输出到单独的文件中,方便后续分析(避免反复执行命令):

# 筛选近1小时内的Exception日志,输出到error.log文件中
grep "2026-02-10 13:" app.log | grep -i "Exception" > error.log

小技巧:如果日志是滚动日志(比如app.log.1、app.log.2),可以用grep “关键词” app.log* 批量筛选所有日志文件。

三、僵尸进程:产生原因+清理方法(避免占用系统资源)

僵尸进程(Zombie)是线上常见的“隐形杀手”——它本身不占用CPU和内存,但会占用系统进程号(PID),如果大量堆积,会导致系统无法创建新进程,最终引发服务异常。

1. 先搞懂:僵尸进程是什么?怎么产生?

  • 定义:僵尸进程是指子进程已经终止,但父进程没有调用wait()或waitpid()函数回收子进程资源,导致子进程残留的“空壳进程”。
  • 核心原因:父进程异常退出、父进程逻辑缺陷(未回收子进程)、子进程执行时间过短,父进程还没来得及回收。

2. 第一步:查找系统中的僵尸进程

用ps命令筛选出僵尸进程,僵尸进程的状态标记为「Z」:

ps -ef | grep defunct  # defunct是僵尸进程的标识
# 或者更精准的筛选
ps aux | awk '{if($8=="Z") print $0}'
  • 输出解读:筛选结果中,STAT列显示为Z的,就是僵尸进程,记住对应的PID和父进程PID(PPID)。

3. 第二步:清理僵尸进程(两种方法,按需选择)

清理僵尸进程的核心是“回收子进程资源”,优先用温和方法,避免影响正常服务:

方法1:重启父进程(推荐,温和安全)

僵尸进程是父进程未回收导致的,重启父进程后,系统会自动回收其下属的僵尸进程:

# 先查看父进程名称(PPID是父进程ID)
ps -ef | grep 1234  # 1234是父进程PPID
# 重启父进程(根据自己的服务启动方式调整)
systemctl restart 服务名  # 比如systemctl restart java-service

方法2:强制杀死父进程(紧急情况使用)

如果父进程无法重启,可强制杀死父进程,系统会将僵尸进程托管给init进程(PID=1),init进程会自动回收僵尸进程:

kill -9 1234  # 1234是父进程PPID,谨慎使用!

警告:强制杀死父进程会导致父进程对应的服务中断,仅在紧急情况下使用,提前做好备份。

四、磁盘满:inode耗尽与文件删除后空间未释放(坑点规避)

线上服务突然宕机,登录服务器后发现磁盘满了(df -h查看使用率100%),但删除大文件后,磁盘空间还是没释放——这两个坑,很多程序员都踩过。

1. 坑点1:文件删除后,空间未释放(原因+解决)

核心原因:

删除的文件正在被进程占用(比如日志文件被Java进程占用),此时rm命令只是删除了文件的目录项,文件的实际内容还在磁盘中,空间不会释放。

排查+解决步骤:

查找被占用的已删除文件:

lsof | grep deleted  # 筛选出已删除但仍被进程占用的文件

找到对应的进程(PID),重启该进程(释放文件占用):

systemctl restart 服务名  # 比如重启Java服务,释放日志文件占用

补充说明:如果无法重启进程,可通过echo “” > 文件名 的方式清空文件(避免删除文件导致的占用问题):

echo "" > app.log  # 清空日志文件,释放空间,且不影响进程占用

2. 坑点2:inode耗尽(磁盘有空间,但无法创建文件)

核心原因:

磁盘的inode节点耗尽了——inode是文件的索引,每个文件对应一个inode,即使磁盘有剩余空间,inode耗尽后,也无法创建新文件(报错:no space left on device)。

排查+解决步骤:

查看inode使用情况:

df -i  # 查看各分区inode使用率,Ifree列是剩余inode数量

定位inode占用最多的目录(找到大量小文件的目录):

# 从根目录开始查找,统计每个目录的inode数量
for i in /*; do echo $i; find $i | wc -l; done

解决方法:删除目录下的大量小文件(比如日志碎片、临时文件),释放inode:

# 批量删除指定目录下的小文件(谨慎操作,确认文件可删除)
rm -rf /tmp/*  # 比如删除/tmp目录下的临时文件

预防建议:定期清理临时文件、日志碎片,避免小文件堆积导致inode耗尽,可写定时脚本自动清理。

五、总结

线上故障排查的核心不是“瞎试错”,而是“找对逻辑、用对命令”——CPU飙高找线程、日志繁杂用grep、僵尸进程清父进程、磁盘满分两种坑点,按本文的套路来,大部分高频故障都能在10分钟内定位并解决。

以上就是Java线程CPU飙高、僵尸进程和磁盘满的排查技巧的详细内容,更多关于Java线程CPU飙高、僵尸进程和磁盘满的资料请关注脚本之家其它相关文章!

相关文章

  • Java开发中的23种设计模式详解(推荐)

    Java开发中的23种设计模式详解(推荐)

    本篇文章主要介绍了Java开发中的23种设计模式详解,现在分享给大家,也给大家做个参考。感兴趣的小伙伴们可以参考一下。 设计模式(Design Patterns)
    2016-11-11
  • SpringMVC使用JsonView针对统一实体返回不同信息

    SpringMVC使用JsonView针对统一实体返回不同信息

    这篇文章主要为大家介绍了SpringMVC使用JsonView针对统一实体返回不同信息,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • mybatis中的多重if 条件判断

    mybatis中的多重if 条件判断

    这篇文章主要介绍了mybatis中的多重if 条件判断,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 排序算法图解之Java希尔排序

    排序算法图解之Java希尔排序

    希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法,其也是一种特殊的插入排序,即将简单的插入排序进行改进后的一个更加高效的版本,也称缩小增量排序。本文通过图片和示例讲解了希尔排序的实现,需要的可以了解一下
    2022-11-11
  • Java实现表白小程序

    Java实现表白小程序

    本文讲述了Java实现表白的代码实例。具有很好的参考价值,希望对大家有所帮助,一起跟随小编过来看看吧,具体如下:
    2018-05-05
  • spring boot国际化之MessageSource的使用方法

    spring boot国际化之MessageSource的使用方法

    这篇文章主要给大家介绍了spring boot国际化之MessageSource使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 深入浅出MyBatis中映射文件和实体类的关联性

    深入浅出MyBatis中映射文件和实体类的关联性

    这篇文章主要介绍了MyBatis中映射文件和实体类的关联性的相关知识,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-09-09
  • apollo与springboot集成实现动态刷新配置的教程详解

    apollo与springboot集成实现动态刷新配置的教程详解

    这篇文章主要介绍了apollo与springboot集成实现动态刷新配置,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • springboot集成Feign的实现示例

    springboot集成Feign的实现示例

    Feign是声明式HTTP客户端,用于简化微服务之间的REST调用,本文就来介绍一下springboot集成Feign的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-11-11
  • SpringBoot越权和数据权限控制的实现方案(最新整理)

    SpringBoot越权和数据权限控制的实现方案(最新整理)

    文章介绍通过自定义注解@PermissionCheck、切面编程和用户权限服务实现Java权限控制,限制只读用户操作,优化数据库查询并统一异常处理,确保系统安全与扩展性,本文给大家介绍SpringBoot越权和数据权限控制的实现方案,感兴趣的朋友跟随小编一起看看吧
    2025-06-06

最新评论