oom

266 阅读2分钟

一、jvm调优 

调优目的:

减少full GC的频率

缩短full GC停顿时间

jvm调优主要就是调整下面两个指标:

停顿时间:垃圾收集器做垃圾回收中断应用执行时间。 -xx:MacGCPauseMillis

吞吐量:垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为 1-1/(1+n)

GC调优步骤:

  1. 打印GC日志

  2. 分析日志得到关键性指标

  3. 分析GC原因,调优jvm参数

二、记一次oom异常

1、收到告警:90%-old-gen-used-on-127.0.0.1:8080   应用老年代内存使用率超过90%

**2、**监控平台上并没有集成内存分析的工具,需要自行dump分析

拿到dump文件

  1. 首先,登录生产环境,

  2. 然后, jps 找到当前java应用的进程pid,或在 Linux 服务器上用 top -c 命令来找是哪个进程 。

  3. 最后,可通过 jmap 命令来获取 HeapDump 文件 :                                               jmap -dump:format=b,file=filename pid                                                           例如: jmap -dump:format=b,file=dumpfilename 3331 最后的数字是进程 id 。

分析dump文件

工具:JDK自带的 VisualVM   或者 外部内存分析工具MAT

使用软件分析可得出:

1、会提示具体是执行到哪个线程抛出的 oom 异常。而且可以点进去查看更详细的信息。

2、点击 Classes ,然后按照 size 排序。 会发现某一个类占用比特别高。右键这个类 Show in Instances View ,会跳转到 Instances 视图。

3、然后找 GC Root,根据 GC 规则,如果能找到 GC Root ,那么这个类是不会被回收的。

最终找到原因是因为 异常数据:遇到大对象data 是一个列表,也就是在执行查询后返回几十万数据。而且此类查询一直在执行。占用的内存很难释放。

相似参考:

juejin.im/post/684490…

deadlion.cn/2017/06/25/…

实际案例:

频繁FGC的真凶原来是它 juejin.cn/post/684490… 

2. 使用 VisualVM 分析堆转储文件 (Heap Dump)

当OOM发生,生成了 .hprof 文件后,你就可以用 jvisualvm 来分析了。

  1. 打开 VisualVM

  2. 载入堆转储文件:通过菜单 File -> Load...,选择生成的 .hprof 文件。

  3. 分析“罪证”:文件加载后,VisualVM 会提供多个视图,其中最有用的是 “Classes”“OQL Console” 视图。

重点查看区域:

  • Summary (摘要):先看这里,确认是因为堆内存溢出(Java heap space) 还是元空间溢出(Metaspace)。这决定了你接下来的分析方向。

    • 如果是 Java heap space:继续下面的步骤。

    • 如果是 Metaspace:问题通常是加载了过多的类(如动态代理、反射生成类过多),你需要查看类加载器的信息。

  • Classes (类视图)

    • 这里按类名分组,列出了所有类的实例数量和总大小。

    • 点击表头“Size (Bytes)”进行降序排序。排在最前面的几个类,就是占用内存最多的“嫌疑犯”。

  • Instances (实例视图)

    • 在“Classes”视图中双击你怀疑的类(比如 byte[]String 或某个自定义类),会切换到该类的实例视图。

    • 你可以看到这个类的成千上万个具体实例。

    • 右键点击某个实例 -> “Show In Instances View” -> “by incoming references”。这会显示是持有着对这个实例的引用,从而帮你一步步追溯到你的业务代码中创建并持有这些对象的根源。这是找到问题代码的关键步骤

3. 实战案例:一个典型的OOM分析流程

假设你的程序因为 Java heap space 而OOM了。

  1. 你载入堆转储文件后,在 Classes 视图按 Size 排序。

  2. 发现排名第一的是 byte[],占用了 90% 的内存。

  3. 你双击 byte[],在 Instances 视图里看到一大堆巨大的字节数组。

  4. 你右键其中一个巨大的 byte[] -> “Show In Instances View” -> “by incoming references”。

  5. VisualVM 会显示这个 byte[] 被哪个对象引用着。比如,你发现它被一个 BufferedImage 对象引用着。

  6. 你再右键这个 BufferedImage 对象,同样查看“incoming references”,可能发现它被你的某个 User 对象引用着,而该 User 对象又被一个静态的 Map 所引用。

  7. 至此, root cause 就找到了:你的代码里有一个静态的 Map 缓存了用户信息,其中包括用户的大头像图片,而这个缓存只增不减,最终吃光了所有堆内存。