当前位置: 代码网 > it编程>编程语言>Java > Java内存异常与CPU过高线上排查完整步骤

Java内存异常与CPU过高线上排查完整步骤

2026年07月17日 Java 我要评论
前言本文档面向线上 java 应用运维与开发人员,提供内存溢出(oom)和 cpu 占用过高问题的定位与解决思路。内含多个实战案例,初级程序员可按步骤复现、定位并修复。一、内存异常(oom)排查与解决

前言

本文档面向线上 java 应用运维与开发人员,提供内存溢出(oom)和 cpu 占用过高问题的定位与解决思路。内含多个实战案例,初级程序员可按步骤复现、定位并修复。

一、内存异常(oom)排查与解决

1.1 常见内存异常类型

类型说明常见原因
java.lang.outofmemoryerror: java heap space堆内存不足对象过多、大对象、内存泄漏
java.lang.outofmemoryerror: gc overhead limit exceededgc 回收时间过长堆几乎满,gc 频繁且效果差
java.lang.outofmemoryerror: metaspace元空间不足类加载过多、动态代理类膨胀
java.lang.outofmemoryerror: direct buffer memory直接内存不足nio、netty 等使用堆外内存超限
java.lang.outofmemoryerror: unable to create new native thread无法创建线程线程数过多、栈内存不足

1.2 启动前必须配置的 jvm 参数

# 堆内存溢出时自动 dump,便于事后分析
-xx:+heapdumponoutofmemoryerror
-xx:heapdumppath=/path/to/dumps/
​
# 可选:限制 metaspace(避免无限增长)
-xx:maxmetaspacesize=256m

1.3 堆内存溢出(heap space)排查步骤

step 1:拿到 heap dump 文件

  • 若已配置 heapdumponoutofmemoryerror,oom 后会自动生成 .hprof 文件

  • 若未配置,可手动触发:jmap -dump:live,format=b,file=heap.hprof <pid>

step 2:分析 heap dump

推荐工具:mat(eclipse memory analyzer)jprofilervisualvm

  • 打开 .hprof 文件

  • 查看 dominator tree / leak suspects,定位占用内存最大的对象

  • 关注:大集合、缓存、未关闭的连接、长生命周期的引用

step 3:常见根因与对应处理

根因处理思路
缓存未限制大小使用带容量和淘汰策略的缓存(caffeine、guava cache 设置 maxsize)
大集合无限增长控制集合上限、定期清理、改为流式处理
大对象/大数组分批处理、压缩、使用流式 api
连接/资源未关闭使用 try-with-resources、finally 关闭、连接池超时检测
内存泄漏(对象被长期引用)检查静态集合、监听器未移除、threadlocal 未清理

1.4 metaspace 溢出排查

# 查看 metaspace 使用情况
jstat -gc <pid>
  • 常见原因:大量动态类生成(反射、代理、groovy 等)、类加载器泄漏

  • 处理:限制 maxmetaspacesize、减少动态类生成、排查类加载器泄漏

1.5 direct buffer / 直接内存溢出

  • 常见于 netty、nio、直接 bytebuffer

  • 检查 -xx:maxdirectmemorysize 配置

  • 排查:bytebuf、directbytebuffer 是否正确 release

1.6 无法创建线程(unable to create new native thread)

# 查看线程数
jstack <pid> | grep "java.lang.thread.state" | wc -l
  • 常见原因:线程池配置过大、线程泄漏、每个线程栈过大

  • 处理:合理配置线程池、减小 -xss、排查线程泄漏

二、cpu 过高排查与解决

2.1 快速定位占用 cpu 高的线程

step 1:找到高 cpu 的 java 进程

# linux
top -c
# 或
ps -eo pid,pcpu,pmem,cmd --sort=-pcpu | head -20

step 2:找到进程内的高 cpu 线程

# 查看进程内各线程 cpu
top -hp <pid>
​
# 或
ps -elo pid,tid,pcpu,comm | grep <pid>

step 3:将线程 id 转为十六进制

# 假设 tid=12345
printf "%x\n" 12345
# 输出:3039

step 4:抓取线程栈并过滤

jstack <pid> > jstack.txt
# 在 jstack.txt 中搜索 nid=0x3039(十六进制)

step 5:多次采样确认

# 连续抓 3 次,间隔 2 秒,对比是否同一处代码
for i in 1 2 3; do jstack <pid> >> jstack_$i.txt; sleep 2; done

2.2 常见 cpu 过高根因

根因表现处理思路
死循环 / 无限递归栈中同一方法反复出现修复逻辑、加退出条件、限制递归深度
正则回溯栈中有 java.util.regex简化正则、避免灾难性回溯
大对象 / json 序列化栈中有 jackson、fastjson 等优化数据结构、减少序列化体积
gc 过于频繁栈中有 gc 相关线程调大堆、优化 gc 参数、减少短生命周期对象
加密 / 计算密集栈中有加密、压缩、复杂计算加缓存、异步、降频、换算法
大量线程竞争栈中有 parkwait、锁减少锁粒度、优化并发设计

2.3 使用 arthas 快速排查(推荐)

# 启动 arthas
java -jar arthas-boot.jar
# 查看最忙的线程
thread
# 查看指定线程栈
thread <id>
# 查看 cpu 使用率 top n 线程
thread -n 5

三、实战案例:从现象到修复

以下案例均以「现象 → 排查步骤 → 根因分析 → 修复方案」的形式呈现,方便初级程序员按图索骥。

案例 1:hashmap 缓存无限增长导致 oom

现象

java.lang.outofmemoryerror: java heap space
    at java.util.hashmap.resize(hashmap.java:...)
    at com.example.orderservice.cacheorder(orderservice.java:42)

问题代码(简化)

// 错误:使用静态 hashmap 做缓存,没有大小限制,也没有过期
public class orderservice {
    private static final map<string, order> cache = new hashmap<>();
    public order getorder(string orderid) {
        if (cache.containskey(orderid)) {
            return cache.get(orderid);
        }
        order order = queryfromdb(orderid);
        cache.put(orderid, order);  // 无限往里塞,迟早 oom
        return order;
    }
}

排查步骤

  1. 拿到 oom 时的 heap dump:jmap -dump:live,format=b,file=heap.hprof <pid>

  2. 用 mat 打开,点击 leak suspectsdominator tree

  3. 找到占用最大的对象,例如:hashmap$node[]hashmap,查看其被谁引用

  4. 通常会看到 orderservice.cache 引用了大量 order 对象

修复方案

// 使用 caffeine 或 guava cache,限制大小和过期时间
public class orderservice {
    private static final cache<string, order> cache = caffeine.newbuilder()
            .maximumsize(10_000)           // 最多 1 万条
            .expireafterwrite(30, timeunit.minutes)
            .build();
    public order getorder(string orderid) {
        return cache.get(orderid, this::queryfromdb);
    }
}
// maven: com.github.ben-manes.caffeine:caffeine

案例 2:一次性加载大 list 导致 oom

现象

java.lang.outofmemoryerror: java heap space
    at java.util.arrays.copyof(arrays.java:...)
    at com.example.reportservice.exportall(reportservice.java:28)

问题代码(简化)

// 错误:一次性从 db 查出百万条记录到内存
public void exportall() {
    list<order> all = ordermapper.selectall();  // 假设 100 万条
    for (order o : all) {
        writetoexcel(o);
    }
}

排查步骤

  1. 用 mat 打开 heap dump,dominator tree 中会看到巨大的 arraylistobject[]

  2. 查看其元素类型,往往是业务实体(如 order

  3. 在代码中搜索 selectallfindall 等全量查询方法

修复方案

// 改为分页或流式查询
public void exportall() {
    int pagesize = 1000;
    int offset = 0;
    list<order> batch;
    while (!(batch = ordermapper.selectbypage(offset, pagesize)).isempty()) {
        for (order o : batch) {
            writetoexcel(o);
        }
        offset += pagesize;
    }
}
// 或使用 mybatis 流式查询:@select 配合 resulthandler

案例 3:死循环导致 cpu 100%

现象

  • 监控显示某 java 进程 cpu 持续 90%+,甚至 100%

  • 接口响应变慢或超时

问题代码(简化)

// 错误:某个条件永远不满足,导致死循环
public void processqueue() {
    while (true) {
        task task = queue.poll();
        if (task == null) {
            // 忘记 break 或 sleep,空转死循环!
            continue;
        }
        dotask(task);
    }
}

排查步骤(按顺序执行)

# 1. 找到高 cpu 进程
top -c
# 记下 pid,例如 12345

# 2. 看该进程内哪些线程 cpu 高
top -hp 12345
# 记下 tid,例如 12346

# 3. 转为十六进制
# linux/mac:
printf "%x\n" 12346
# windows powershell:
[convert]::tostring(12346, 16)
# 输出:303a

# 4. 抓线程栈
jstack 12345 > jstack.txt

# 5. 在 jstack.txt 中搜索 nid=0x303a

你会看到的栈(示例)

"http-nio-8080-exec-1" #32 daemon prio=5 ...
   java.lang.thread.state: runnable
        at com.example.queueprocessor.processqueue(queueprocessor.java:18)
        at com.example.queueprocessor$$lambda$1.run(...)

说明:processqueue 第 18 行附近在疯狂运行。

修复方案

// 队列为空时应等待,而不是空转
public void processqueue() {
    while (true) {
        task task = queue.poll();
        if (task == null) {
            locksupport.parknanos(timeunit.milliseconds.tonanos(100));
            continue;
        }
        dotask(task);
    }
}

案例 4:正则灾难性回溯导致 cpu 飙升

现象

  • 某个包含用户输入的接口被调用后,cpu 突然飙高

  • 接口长时间不返回

问题代码(简化)

// 危险正则:嵌套量词 + 回溯,可能指数级耗时
public boolean validateemail(string input) {
    // 这个正则在恶意输入下会灾难性回溯
    return input.matches("([a-z]+\\.)*[a-z]+@[a-z]+\\.com");
}
// 恶意输入示例:"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"

排查步骤

  1. top -hp + jstack 定位高 cpu 线程

  2. 栈中会看到类似:

   java.lang.thread.state: runnable
        at java.util.regex.pattern$loop.match(pattern.java:...)
        at java.util.regex.pattern$grouptail.match(pattern.java:...)
        at java.util.regex.pattern$branchconn.match(pattern.java:...)
        ...

说明:卡在正则匹配上。

修复方案

// 简化正则,避免嵌套量词;或对输入长度做限制
public boolean validateemail(string input) {
    if (input == null || input.length() > 100) return false;
    return input.matches("[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}");
}

案例 5:线程池任务里又有死循环

现象

  • 线程池满,新任务一直排队

  • cpu 高,但 jstack 里很多线程状态是 runnable,且栈顶都在同一业务方法

问题代码(简化)

@scheduled(fixedrate = 1000)
public void scheduledtask() {
    executor.submit(() -> {
        for (int i = 0; i < list.size(); i++) {
            // 某次循环里 list 被清空,size 变成 0,但 i 已经很大...
            // 或者条件写错,i 永远不会满足退出条件
            process(list.get(i));
        }
    });
}

排查步骤

  1. jstack <pid> 抓栈

  2. 搜索业务类名(如 scheduledtaskprocess

  3. 看多根线程是否都卡在同一行,且状态为 runnable

修复思路

  • 检查循环的退出条件是否正确

  • 避免在循环内修改 list(如并发清空)

  • 加日志或断点确认 ilist.size() 的变化

案例 6:arthas 一键定位 cpu 问题(推荐)

当你不熟悉 topjstack 时,可用 arthas 快速定位。

步骤

# 1. 下载并启动 arthas
curl -o https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

# 2. 选择要 attach 的 java 进程(按数字回车)

# 3. 看 cpu 最高的线程
thread -n 3

# 输出示例:
# threads total: 45, new: 0, runnable: 10, blocked: 0, waiting: 30, timed_waiting: 5
# "http-nio-8080-exec-2" id=32 cpuusage=87.23%
#     at com.example.badservice.heavycompute(badservice.java:66)
#     ...

直接看到 badservice.java:66 在吃 cpu,去查该行代码即可。

案例速查表

现象优先怀疑快速验证
oom: heap space大集合、无界缓存mat 看 dominator tree 最大的对象
oom: metaspace动态类过多jstat -gc 看 mc/mu
cpu 100%死循环、正则、大计算jstack 或 arthas thread -n 5
接口卡住死锁、阻塞 iojstack 看 blocked / waiting

四、常用命令速查

场景命令
查看 jvm 进程jps -lps -ef \| grep java
堆内存概览jmap -heap <pid>
生成 heap dumpjmap -dump:live,format=b,file=heap.hprof <pid>
线程栈jstack <pid> > jstack.txt
gc 统计jstat -gc <pid> 1000 10
查看 jvm 参数jinfo -flags <pid>

五、预防建议

  1. 上线前:配置 heapdumponoutofmemoryerror、合理设置堆和 metaspace

  2. 监控:接入 jvm 监控(堆、gc、线程数、cpu)

  3. 压测:做压测和长时间稳定性测试,提前暴露问题

  4. 限流与熔断:防止突发流量导致 oom 或 cpu 打满

  5. 代码:避免大对象、注意资源释放、合理使用缓存

文档更新:可根据实际环境补充具体参数和工具版本。

到此这篇关于java内存异常与cpu过高线上排查完整步骤的文章就介绍到这了,更多相关java内存异常与cpu过高内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com