写点什么

JVM 实战—OOM 的定位和解决

  • 2025-01-07
    福建
  • 本文字数:9350 字

    阅读完需:约 31 分钟

1.如何对系统的 OOM 异常进行监控和报警


(1)最佳的解决方案


最佳的 OOM 监控方案就是:建立一套监控平台,比如搭建 Zabbix、Open-Falcon 之类的监控平台。如果有监控平台,就可以接入系统异常的监控和报警,可以设置当系统出现 OOM 异常,就发送报警给对应的开发人员。这是中大型公司里最常用的一种方案。

 

监控平台一般会监控系统的如下几个层面:

一.机器资源(CPU、磁盘、内存、网络)的负载

二.JVM 的 GC 频率和内存使用率

三.系统自身的业务指标

四.系统的异常报错

 

(2)搭建系统监控体系的建议


建议一:对机器指标进行监控


一.首先可以看到机器的资源负载情况


比如 CPU 负载,可以看到现在 CPU 目前的使用率有多高。比如磁盘 IO 负载,包括磁盘上发生了多少数据量的 IO、一些 IO 耗时等。当然一般业务系统不会直接读写本地磁盘,最多就是写一些本地日志。但是一般业务系统也应该关注本地磁盘的使用量和剩余空间,因为有的系统可能会因为一些代码 bug,导致一直往本地磁盘写东西。万一把磁盘空间写满了就麻烦了,磁盘满了也会导致系统无法运行。

 

二.其次可以看到机器的内存使用量


这个是从机器整体层面去看的,看看机器对内存使用的一些变化。当然内存这块,比较核心的还是 JVM 的监控,通过监控平台可以看到 JVM 各个内存区域的使用量的变化的。

 

三.还有一个比较关键的是 JVM 的 FGC 频率


一般会监控一段时间内的 FGC 次数,比如 5 分钟内发生了几次 FGC。

 

四.最后就是机器上的网络负载


就是通过网络 IO 读写了多少数据、一些耗时等。

 

其实线上机器最容易出问题的主要有三方面:


一是 CPU(必须要监控 CPU 的使用率),如果 CPU 负载过高(如长期超过 90%)就报警。

二是内存(必须要监控内存的使用率),如果机器内存使用率超过一定阈值(如超 90%)则可能内存不够。

三是 JVM 的 FGC 频率,假设 5 分钟内发生了 10 次 FGC,那一定是频繁 FGC 了。

 

建议二:对业务指标进行监控


另外比较常见的就是对系统的业务指标进行监控。比如在系统每创建一个订单时就上报一次监控,然后监控系统会收集 1 分钟内的订单数量。这样就可以设定一个阈值,比如 1 分内要是订单数超过 100 就报警。因为订单过多可能涉及一些刷单行为,这就是业务指标监控。

 

建议三:对系统异常进行监控


最后就是对系统中所有的 try catch 异常报错,都接入报警。一旦发现有 try catch 异常,就上报到监控平台。然后监控平台就能通告这次异常,让相关负责人收到报警。比如一旦发现有 OOM 异常,就能马上通知相关开发人员。

 

2.如何在 JVM 内存溢出时自动 dump 内存快照


(1)解决 OOM 问题的一个初步思路


如果发生 OOM,则说明系统中某个区域的对象太多,塞满了那个区域。而且一定是无法回收掉区域中的那些对象,最终才会导致内存溢出。

 

要解决 OOM,首先得知道是什么对象太多了,从而最终导致 OOM 的。所以必须有一份 JVM 发生 OOM 时的 dump 内存快照,只要有了那个 dump 内存快照,就可以用 MAT 工具分析什么对象太多了。

 

那么现在一个关键问题来了:到底怎么做才可以在 JVM 内存溢出时自动 dump 出一份内存快照呢?

 

(2)在 OOM 时自动 dump 内存快照


如果 JVM 发生 OOM,JVM 是不是完全来不及处理然后突然进程就没了?也就是 JVM 是不是非常突然的、自己都无法控制,就挂掉了?

 

其实不是的,JVM 在发生 OOM 之前会尽可能进行 GC 腾出一些内存空间。如果 GC 后还是没有空间,放不下对象, 才会触发内存溢出。所以 JVM 自己对 OOM 情况的发生是完全有把控权的。

 

JVM 知道什么时候会触发 OOM,它是在无法放下对象的时候才会触发的。因此 OOM 的发生并不是突然内存太多,连 JVM 自己都没反应过来就崩溃。

 

JVM 如果知道自己将要发生 OOM 了,那么此时完全可以让它做点事情。比如可以让 JVM 在 OOM 时 dump 一份内存快照,事后只要分析这个内存快照,就可以知道是哪些对象导致 OOM 的了。为此,需要在 JVM 的启动参数中加入如下参数:


 -XX:+HeapDumpOnOutOfMemoryError   -XX:HeapDumpPath=/usr/local/app/oom
复制代码


第一个参数的意思是:在 OOM 时自动进行 dump 内存快照。第二个参数的意思是:把内存快照放到哪里。只要加入了这两个参数,可以事后再获取 OOM 时的内存快照进行分析。

 

(3)一份比较全面的 JVM 参数模板


 -Xms4096M -Xmx4096M -Xmn3072M -Xss1M  -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC  -XX:CMSInitiatingOccupancyFaction=92  -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0  -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark  -XX:+DisableExplicitGC  -XX:+PrintGCDetails -Xloggc:gc.log  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom
复制代码


这份 JVM 参数模板基本上涵盖了所有需要的一些参数。首先是各个内存区域的大小分配,这个需要精心调优的。其次是两种垃圾回收器的指定。接着是一些常规的 CMS 垃圾回收参数,可优化偶尔发生的 FGC 性能。最重要的,就是平时要打印出 GC 日志。GC 日志可以配合 jstat 工具分析 GC 频率和性能的时候使用。jstat 可分析出 GC 频率,但每次具体的 GC 情况则要结合 GC 日志来看。还有次重要的,就是发生 OOM 时能自动 dump 内存快照。这样即使突然发生 OOM,且事后才知道,都可以分析当时的内存快照。

 

3.Metaspace 区域内存溢出时应如何解决(OutOfMemoryError: Metaspace)

 

(1)解决思路


面对 Metaspace 区域内存溢出,思路如下:首先分析 GC 日志,然后再让 JVM 自动 dump 出内存快照。最后用 MAT 来分析这份内存快照,从内存快照里寻找 OOM 的原因。

 

(2)示例代码


public class Demo1 {    public static void main(String[] args) {        long counter = 0;        while (true) {            Enhancer enhancer = new Enhancer();            enhancer.setSuperclass(Car.class);            enhancer.setUseCache(false);            enhancer.setCallback(new MethodInterceptor() {                @Override                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {                    if (method.getName().equals("run")) {                        System.out.println("Check before run");                        return methodProxy.invokeSuper(o, objects);                    } else {                        return methodProxy.invokeSuper(o, objects);                    }                }            });

Car car = (Car) enhancer.create(); car.run(); System.out.println("目前创建了" + (++counter) + "个Car类的子类了"); } } static class Car { public void run() { System.out.println("Run..."); } } static class SafeCar extends Car { @Override public void run() { System.out.println("Safe Run..."); super.run(); } }}
复制代码


接着 JVM 参数修改为如下,因为我们想看一下 GC 日志和导出内存快照。


 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
复制代码


注意,上面那个 HeapDumpPath 参数调整为当前项目下,这样方便查看。

 

(3)分析 GC 日志


接下来用上述 JVM 参数运行这段程序,会发现项目下多了两个文件:一个是 gc.log,一个是 java_pid910.hprof。当然不同的机器运行这个程序,导出的 hprof 文件的名字是不太一样的,因为 hprof 文件会用 PID 进程 id 作为文件名字。

 

步骤一:首先分析 gc.log


分析它是如何不断往 Metaspace 区放入大量生成的类,然后触发 FGC 的。接着回收 Metaspace 区,回收后还是无法放下更多类,才抛出 OOM。

 

步骤二:然后用 MAT 分析 OOM 时的内存快照


也就是使用 MAT 工具找到 Metaspace 内存溢出问题的原因,GC 日志如下:


CommandLine flags: -XX:CompressedClassSpaceSize=2097152 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxMetaspaceSize=10485760 -XX:MaxNewSize=348966912 -XX:MaxTenuringThreshold=6 -XX:MetaspaceSize=10485760 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 0.716: [GC (Allocation Failure) 0.717: [ParNew: 139776K->2677K(157248K), 0.0038770 secs] 139776K->2677K(506816K), 0.0041376 secs] [Times: user=0.03 sys=0.01, real=0.00 secs]0.771: [Full GC (Metadata GC Threshold) 0.771: [CMS: 0K->2161K(349568K), 0.0721349 secs] 20290K->2161K(506816K), [Metaspace: 9201K->9201K(1058816K)], 0.0722612 secs] [Times: user=0.12 sys=0.03, real=0.08 secs]0.843: [Full GC (Last ditch collection) 0.843: [CMS: 2161K->1217K(349568K), 0.0164047 secs] 2161K->1217K(506944K), [Metaspace: 9201K->9201K(1058816K)], 0.0165055 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]0.860: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1217K(349568K)] 1217K(506944K), 0.0002251 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]0.860: [CMS-concurrent-mark-start]0.878: [CMS-concurrent-mark: 0.003/0.018 secs] [Times: user=0.05 sys=0.01, real=0.02 secs]0.878: [CMS-concurrent-preclean-start]Heap par new generation   total 157376K, used 6183K [0x00000005ffe00000, 0x000000060a8c0000, 0x0000000643790000)  eden space 139904K,   4% used [0x00000005ffe00000, 0x0000000600409d48, 0x00000006086a0000)  from space 17472K,   0% used [0x00000006086a0000, 0x00000006086a0000, 0x00000006097b0000)  to   space 17472K,   0% used [0x00000006097b0000, 0x00000006097b0000, 0x000000060a8c0000) concurrent mark-sweep generation total 349568K, used 1217K [0x0000000643790000, 0x0000000658cf0000, 0x00000007ffe00000)Metaspace       used 9229K, capacity 10146K, committed 10240K, reserved 1058816K class space    used 794K, capacity 841K, committed 896K, reserved 1048576K
复制代码


一.第一行 GC 日志如下


0.716: [GC (Allocation Failure) 0.717: [ParNew: 139776K->2677K(157248K), 0.0038770 secs] 139776K->2677K(506816K), 0.0041376 secs] [Times: user=0.03 sys=0.01, real=0.00 secs]
复制代码


这行日志,这是第一次 GC,它本身是一个 Allocation Failure 的问题。也就是说,在 Eden 区中分配对象时,已经发现 Eden 区内存不足了,于是就触发了一次 YGC。

 

二.那么这个对象到底是什么对象


回到代码中,可知 Enhancer 是一个对象,它是用来生成类的,如下所示:


Enhancer enhancer = new Enhancer();
复制代码


接着会基于 new Enhancer 生成类对象来生成那个子类的对象,如下所示:


Car car = (Car) enhancer.create();
复制代码


上述代码会在 while(true)里不停创建 Enhancer 对象和 Car 的子类对象,因此 Eden 区慢慢就会被占满了,于是会看到上述日志:


[ParNew: 139776K->2677K(157248K), 0.0038770 secs] 
复制代码


这行日志就是说:在默认的内存分配策略下,新生代一共可用空间是 150M 左右。然后大概用到 140M,Eden 区都占满了,就会触发 Allocation Failure。即没有 Eden 区的空间去分配对象了,此时就只能触发 YGC。

 

三.接着来看如下 GC 日志


0.771: [Full GC (Metadata GC Threshold) 0.771: [CMS: 0K->2161K(349568K), 0.0721349 secs] 20290K->2161K(506816K), [Metaspace: 9201K->9201K(1058816K)], 0.0722612 secs] [Times: user=0.12 sys=0.03, real=0.08 secs]
复制代码


这行日志说明就是发生 FGC 了,而且通过 Metadata GC Threshold 清晰看到,是 Metaspace 区域满了,这时看后面的日志:


20290K->2161K(506816K);
复制代码


这就是说堆内存(新生代+老年代)一共是 500M,有 20M 被使用了,这 20M 的内存是被新生代使用的。

 

然后 FGC 必然会带着一次 YGC,因此这次 FGC 执行了 YGC,所以回收了很多对象,剩下 2161K 的对象,这 2161K 的对象大概就是 JVM 的一些内置对象。

 

然后直接就把这些对象都放入老年代,为什么呢?因为后面的日志:


[CMS: 0K->2161K(349568K), 0.0721349 secs]
复制代码


明显表明,FGC 带着 CMS 进行了老年代的 Old GC,结果人家本来是 0K。然后从新生代转移来了 2161K 的对象,所以老年代变成 2161K 了。

 

接着看日志:


[Metaspace: 9201K->9201K(1058816K)]
复制代码


这说明此时 Metaspace 区域已经使用了差不多 9M 左右的内存了。JVM 发现离限制的 10M 内存很接近了,于是触发了 FGC。但是对 Metaspace 进行 GC 后发现类的对象全部都还存活,因此还是剩余 9M 左右的类在 Metaspace 里。

 

四.接着来看下一行 GC 日志


0.843: [Full GC (Last ditch collection) 0.843: [CMS: 2161K->1217K(349568K), 0.0164047 secs] 2161K->1217K(506944K), [Metaspace: 9201K->9201K(1058816K)], 0.0165055 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
复制代码


接着又是一次 FGC。其中的 Last ditch collection,说明这是最后一次拯救的机会了。因为之前 Metaspace 回收了一次但发现没有类可以回收,所以新的类无法放入 Metaspace 了。因此才再最后试一试 FGC,看看能不能回收掉一些。

 

结果发现还是:


[Metaspace: 9201K->9201K(1058816K)], 0.0165055 secs]
复制代码


这说明 Metaspace 区域还是无法回收掉任何类,Metaspace 区几乎还是占满了设置的 10M 内存。

 

五.继续看如下 GC 日志


0.860: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1217K(349568K)] 1217K(506944K), 0.0002251 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]0.860: [CMS-concurrent-mark-start]0.878: [CMS-concurrent-mark: 0.003/0.018 secs] [Times: user=0.05 sys=0.01, real=0.02 secs]0.878: [CMS-concurrent-preclean-start]Heap par new generation   total 157376K, used 6183K [0x00000005ffe00000, 0x000000060a8c0000, 0x0000000643790000)  eden space 139904K,   4% used [0x00000005ffe00000, 0x0000000600409d48, 0x00000006086a0000)  from space 17472K,   0% used [0x00000006086a0000, 0x00000006086a0000, 0x00000006097b0000)  to   space 17472K,   0% used [0x00000006097b0000, 0x00000006097b0000, 0x000000060a8c0000) concurrent mark-sweep generation total 349568K, used 1217K [0x0000000643790000, 0x0000000658cf0000, 0x00000007ffe00000)Metaspace       used 9229K, capacity 10146K, committed 10240K, reserved 1058816K class space    used 794K, capacity 841K, committed 896K, reserved 1048576K
复制代码


接着 JVM 就直接退出了,退出的时候打印了当前内存的一个情况:年轻代和老年代几乎没占用。但是 Metaspace 的 capacity 是 10M,使用了 9M 左右。无法再继续使用了,所以触发了内存溢出。

 

此时在控制台会打印出如下的信息:


Caused by: java.lang.OutOfMemoryError: Metaspace    at java.lang.ClassLoader.defineClass1(Native Method)    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)    ... 11 more
复制代码


明确抛出异常表明 OutOfMemoryError,原因就是 Metaspace 区域满了。

 

因此可以假设是 Metaspace 内存溢出了,然后我们自己监控到了异常。此时直接去线上机器看一下 GC 日志和异常信息。通过上述分析就能知道系统是如何运行、触发几次 GC 后引发内存溢出。

 

(4)分析内存快照


当我们知道是 Metaspace 引发的内存溢出后:就把内存快照文件从线上机器拷回本地电脑,打开 MAT 工具进行分析。如下图示:



从上图可以看到实例最多的就是 AppClassLoader。为什么会有这么多的 ClassLoader 呢?一看就是 CGLIB 之类的东西在动态生成类的时候搞出来的,此时我们可以点击上图的 Details 进去看看。



为什么有一大堆在 Demo1 中动态生成的 Car$$EnhancerByCGLIB 类?看到截图中的 Object 数组里出现的很多这种类,就已经很清晰说明了:某个类生成了大量动态类 EnhancerByCGLIB,填满了 Metaspace 区。所以此时直接去代码里排查动态生成类的代码即可。

 

解决这个问题的办法也很简单:对 Enhancer 进行缓存,只需要一个 Enhancer 实例即可,不需要无限制地生成类。

 

4.JVM 栈内存溢出时应如何解决(StackOverflowError)


(1)栈内存溢出能否按照之前的方法解决


也就是说,GC 日志、内存快照,这些东西对解决栈内存溢出有帮助吗?其实栈内存溢出跟堆内存是没有关系的,因为它的本质是一个线程的栈中压入了过多调用方法的栈桢。比如调用了几千次方法就会压入几千个方法栈桢,此时就会导致线程的栈内存不足,无法放入更多栈桢。

 

GC 日志对栈内存溢出是没有用的,因为 GC 日志主要分析的是堆内存和 Metaspace 区域的一些 GC 情况。就线程的栈内存和栈桢而言,不存在所谓的 GC。

 

线程调用一个方法时,会在线程的虚拟机栈里压入方法栈桢。接着线程执行完该方法后,方法栈桢会从线程的虚拟机栈里出栈。最后一个线程运行完毕时,它的虚拟机栈内存就会被释放。

 

所以本身栈内存不存在所谓的 GC 和回收。线程调用方法时就会给方法栈桢分配内存,线程执行完方法时就会回收掉那个方法栈桢的内存。

 

内存快照是分析内存占用的,同样是针对堆内存和 Metaspace 的。所以对线程的栈内存而言,也不需要使用内存快照。

 

(2)代码示例


public class Demo2 {    public static long counter = 0;    public static void main(String[] args) {        work();    }    public static void work() {        System.out.println("目前是第" + (++counter) + "次调用方法");        work();    }}
复制代码


使用的 JVM 参数如下:


 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:ThreadStackSize=1m  -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
复制代码


(3)运行代码后分析异常报错信息的调用栈


接着运行代码产生栈内存溢出,如下:


目前是第5549次调用方法java.lang.StackOverflowError  at java.io.FileOutputStream.write(FileOutputStream.java:326)  at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)  at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)  at java.io.PrintStream.write(PrintStream.java:482)  at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)  at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)  at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)  at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)  at java.io.PrintStream.write(PrintStream.java:527)  at java.io.PrintStream.print(PrintStream.java:669)  at java.io.PrintStream.println(PrintStream.java:806)  at com.demo.rpc.test.Demo.work(Demo.java:9)  at com.demo.rpc.test.Demo.work(Demo.java:10)
复制代码


上述所示异常会明确提示:出现栈内存溢出的问题,原因是不断调用 Demo 类的 work()方法导致的。

 

因此对于栈内存溢出的问题,定位和解决的思路就很简单了。只要把所有的异常都写入本地日志文件,那么发现系统崩溃时,去日志里定位异常信息即可。

 

(4)总结


情形一:Metaspace 区域溢出


通过异常信息可以直接定位出是 Metaspace 区域发生异常,然后分析 GC 日志就可以知道 Metaspace 发生溢出的全过程,接着再使用 MAT 分析内存快照,就知道是哪个类太多导致异常。

 

情形二:栈内存溢出


首先从异常日志中就能知道是栈内存溢出。

然后从异常日志中可以找到对应的报错方法。

知道哪个方法后,就可以到代码中定位问题。

 

5.JVM 堆内存溢出时应该如何解决(OutOfMemoryError: Java heap space)

 

(1)示例代码


运行如下程序:


public class Demo3 {    public static void main(String[] args) {        long counter = 0;        List<Object> list = new ArrayList<Object>();        while(true) {            list.add(new Object());            System.out.println("当前创建了第" + (++counter) + "个对象");        }        }}
复制代码


采用的 JVM 参数如下:


 -Xms10m -Xmx10m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
复制代码


(2)运行后的观察


其实堆内存溢出的现象也是很简单的,在系统运行一段时间之后,会发现系统崩溃了,然后登录到线上机器检查日志文件。先看到底为什么崩溃:


java.lang.OutOfMemoryError: Java heap spaceDumping heap to ./java_pid1023.hprof ...Heap dump file created [13409210 bytes in 0.033 secs]    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
复制代码


这个就表明了:是 Java 堆内存溢出了,而且还导出了一份内存快照。此时 GC 日志都不用分析,因为堆内存溢出往往对应着大量的 GC 日志。这些大量的 GC 日志分析起来很麻烦,可以直接将线上自动导出的内存快照拷贝回本地,然后使用 MAT 分析。

 

(3)用 MAT 分析内存快照


采用 MAT 打开内存快照后会看到下图:



这次 MAT 非常简单,直接在内存泄漏报告中告诉我们。内存溢出原因只有一个,因为它没提示任何其他的问题。接下来仔细分析一下 MAT 提供的分析报告。


一.首先看下面的提示


The thread java.lang.Thread @ 0x7bf6a9a98 main keeps local variables with total size 7,203,536 (92.03%) bytes.
复制代码

 

意思是 main 线程通过局部变量引用了 7230536 个字节的对象(7M),考虑到总共就给堆内存 10M,所以 7M 基本上个已经到极限了。

 

二.接着看下面的提示


The memory is accumulated in one instance of "java.lang.Object[]" loaded by "<system class loader>".
复制代码


这句话的意思是内存都被一个实例对象占用了,就是 java.lang.Object[]。

 

三.这时还不能判断,得点击 Details



在 Details 里能看到这个东西,即占用了 7M 内存的的 java.lang.Object[]。这里会列出该 Object 数组的每个元素,可见是大量的 java.lang.Object,而这些 java.lang.Object 其实就是我们在代码里创建的。至此真相大白,就是大量的 Object 对象占用了 7M 的内存导致内存溢出。

 

四.接着分析这些对象是如何创建出来的


此时可以回到上一级页面进行查找,如下:



这个意思是可以查看创建那么多对象的线程,它的一个执行栈。从线程执行栈中就可知这个线程在执行什么方法时创建了大量的对象。



从上面的调用栈可以看到:Demo3.main()方法会一直调用 ArrayList.add()方法,然后引发 OOM。所以接下来只要在对应代码里看一下,就知道怎么回事了。接着优化对应的代码即可,这样就不会发生内存溢出了。

 

(4)总结


堆内存溢出问题的分析和定位:

一是加入自动导出内存快照的参数

二是到线上看一下日志文件里的报错

如果是堆溢出,则用 MAT 分析内存快照。

 

MAT 分析的时候一些顺序和技巧:

一.首先看占用内存最多的对象是谁

二.然后分析那个线程的调用栈

三.接着看哪个方法引发内存溢出

四.最后优化代码即可


文章转载自:东阳马生架构

原文链接:https://www.cnblogs.com/mjunz/p/18656124

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
JVM实战—OOM的定位和解决_JVM_不在线第一只蜗牛_InfoQ写作社区