线上OOM排除

一、OOM 问题本质与分类

OOM(Out Of Memory)​ 表示 JVM 内存不足以分配对象空间。根据 JVM 内存区域划分,常见 OOM 类型包括:

  1. 堆内存溢出(java.lang.OutOfMemoryError: Java heap space)​
    对象分配超过堆内存限制。
  2. 元空间溢出(java.lang.OutOfMemoryError: Metaspace)​
    类元数据(如类定义、方法信息)超过元空间限制。
  3. 直接内存溢出(java.lang.OutOfMemoryError: Direct buffer memory)​
    使用 NIO 分配的堆外内存超出限制。
  4. 栈溢出(java.lang.StackOverflowError)​
    线程调用栈深度超过限制。

二、详细排查步骤(基于图片流程扩展)


1. ​导出内存堆栈(Heap Dump)​

核心目标:获取 JVM 内存中对象分配的完整快照。

(1)手动导出 Heap Dump
# 命令格式:jmap -dump:format=b,file=<文件名> <PID>
jmap -dump:format=b,file=oom_heapdump.hprof 1234
  • jmap:JDK 自带的堆内存分析工具。
  • format=b:二进制格式导出,兼容 MAT 等工具。
  • file=oom_heapdump.hprof:生成的堆转储文件名。
  • 1234:Java 进程的 PID(通过 jps 或 ps -ef | grep java 查看)。
(2)JVM 参数自动导出

在启动脚本中添加参数:

-XX:+HeapDumpOnOutOfMemoryError       # 发生 OOM 时自动生成 Heap Dump
-XX:HeapDumpPath=/path/to/dumps       # 指定 Heap Dump 存储路径
-XX:OnOutOfMemoryError="kill -9 %p"   # 可选:OOM 后终止进程(防止僵尸进程)
(3)解析 Heap Dump 基本信息
jhat -port 7000 oom_heapdump.hprof    # 启动 HTTP 服务器分析 Heap Dump

访问 http://localhost:7000 查看对象分布,但功能较弱,​推荐使用 MAT


2. ​使用 MAT(Memory Analyzer Tool)深度分析

MAT 是 Eclipse 基金会开发的 Java 内存分析工具,支持可视化分析堆转储文件。

(1)安装与启动
(2)关键分析功能
功能作用操作路径
Histogram(直方图)​按类统计对象数量及占用内存OverView → Histogram
Dominator Tree(支配树)​显示哪些对象直接占用最多内存OverView → Dominator Tree
Leak Suspects(泄漏嫌疑)​自动生成内存泄漏报告Reports → Leak Suspects
Path to GC Roots(到 GC Roots 的路径)​查看对象到 GC Roots 的引用链,判断是否被强引用持有右键对象 → Path to GC Roots
(3)实战分析示例
  1. 定位大对象

    • 在 Dominator Tree 中按 Retained Heap(对象及其引用链占用的总内存)排序。
    • 查找占用内存异常的类(如自定义的缓存类、集合类)。
  2. 分析引用链

    • 右键可疑对象 → Merge Shortest Paths to GC Roots → exclude weak/soft references
    • GC Roots:JVM 中不会被垃圾回收的对象(如静态变量、活动线程栈中的局部变量)。
  3. 内存泄漏确认

    • 如果发现大量对象被 ThreadLocal、静态 Map 或未关闭的资源(如数据库连接)引用,则存在内存泄漏。

3. ​结合代码定位问题根源

通过 MAT 找到可疑对象后,需在代码中定位具体逻辑。

(1)代码示例与注释
public class MemoryLeakDemo {
    private static Map<Long, byte[]> cache = new HashMap<>();  // 静态 Map 导致对象无法释放

    public void processRequest(long userId) {
        byte[] data = new byte[1024 * 1024];  // 分配 1MB 内存
        cache.put(userId, data);              // 数据存入静态 Map
        // 业务逻辑处理...
    }
}
  • 问题点 1static Map 是 GC Root,导致所有存入的 byte[] 无法回收。
  • 问题点 2:未清理过期数据,cache 无限增长。
(2)修复方案
public class FixedMemoryDemo {
    private static Map<Long, byte[]> cache = new WeakHashMap<>();  // 使用弱引用 Map

    public void processRequest(long userId) {
        byte[] data = new byte[1024 * 1024];
        cache.put(userId, data);
        // 业务逻辑处理...

        // 添加清理逻辑(如 LRU 策略)
        if (cache.size() > 1000) {
            cache.remove(cache.keySet().iterator().next());
        }
    }
}
  • 改进点 1WeakHashMap 的键是弱引用,当键不再被强引用时,条目自动删除。
  • 改进点 2:手动控制缓存大小,防止无限增长。

三、高级排查技巧

1. ​JVM 内存参数调优
-Xms4g -Xmx4g           # 堆内存初始值=最大值,避免动态扩容
-XX:MetaspaceSize=256m  # 元空间初始大小
-XX:MaxMetaspaceSize=512m
-XX:MaxDirectMemorySize=1g  # 限制直接内存
2. ​监控工具辅助
  • VisualVM:实时监控堆内存、线程、CPU。
  • Arthas:在线诊断工具,支持动态查看对象分布:
    dashboard                 # 查看实时内存/线程状态
    heapdump --live /tmp/dump.hprof  # 导出存活对象堆转储
3. ​代码层预防
  • 避免静态集合类:尽量使用局部变量或弱引用(WeakReference)。
  • 及时关闭资源:数据库连接、文件流等必须显式关闭。
  • 限制缓存大小:使用 Guava Cache 或 Caffeine 并设置过期策略。

参考:腾讯元宝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值