发生OOM怎么处理

怎么感知到OOM

  1. 使用Zabbix、Open-Falcon之类的监控平台,监控和报警,主要对机器(CPU、磁盘、内存、网络)资源的负载情况,JVM的GC频率和内存使用率,系统自身的业务指标,系统的异常报错进行监控
  • CPU使用率都达到100%了,此时一定有问题
  • 关注本地磁盘的使用量和剩余空间
  • 关注机器上的内存使用量
  • 关注机器上的网络负载
  • 注意JVM的Full GC的频率
  1. 另一种是被动等待系统挂掉后客服来通知你

发生OOM的内存区域

方法区(MetaSpace)

方法区用来存放系统里的各种类的信息的,包括JDK自身内置的一些类的信息,都在这块区域里。

  • 方法区发生溢出的情况:
    1. Metaspace区域直接用默认的参数,不设置其大小,导致默认的Metaspace区域可能才几十MB而已,此时对于一个稍微大型一点的系统,因为他自己有很多类,还依赖了很多外部的jar包有有很多的类,几十MB的Metaspace很容易就不够了
    2. 就是很多人写系统的时候会用cglib之类的技术动态生成一些类,一旦代码中没有控制好,导致你生成的类过于多的时候,就很容易把Metaspace给塞满,进而引发内存溢出.
/**
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:MetaspaceSize=10m
    -XX:MaxMetaspaceSize=10m
    -XX:+PrintGCDetails
    -Xloggc:gc.log
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=./
 * @author fmj 模拟元空间内存溢出
 * @date 2021 2021/9/8 15:32
 */
public class MetaSpaceOverFlow {
    public static void main(String[] args) {
        int count = 0;
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Subject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new ProxyClass());
            Subject subject = (Subject)enhancer.create();
            subject.run();
            System.out.printf("正在创建第%d个对象\n", ++count);
        }
    }
}

Java虚拟机栈

每个线程都有一个自己的虚拟机栈,就是所谓的栈内存。然后这个线程只要执行一个方法,就会为方法创建一个栈桢,将栈桢放入自己的虚拟机栈里去,然后在这个栈桢里放入方法中定义的各种局部变量。

  • 线程栈溢出的情况
    1. 如果不停的让线程调用方法,不停的往栈里放入栈桢,此时终有一个时刻,大量的栈桢会消耗完毕这个1MB的线程栈内存,最终就会导致出现栈内存溢出的情况。
    2. 一般来说,其实引发栈内存溢出,往往都是代码里写了一些bug才会导致的,正常情况下发生的比较少。避免出现无限制的方法递归,就一般可以避免栈内存的溢出。
/**
 * @author 模拟虚拟机栈溢出
	 -XX:ThreadStackSize=1m
 * @date 2021 2021/9/8 16:24
 */
public class StackOverflow {

    static int count = 0;

    public static void main(String[] args) {
        work();
    }

    private static void work() {
        System.out.printf("第%d次执行work方法\n", ++count);
        work();
    }
}

堆内存

在写好的代码里,特别在一些方法中,可能会频繁的创建各种各样的对象,这些对象都是放在堆内存里的

  • 堆内存溢出情况
    1. 高并发场景下导致ygc后存活对象太多,高并发场景下,导致ygc后很多请求还没处理完毕,存活对象太多,可能就在Survivor区域放不下了,此时就只能进入到老年代里去了,老年代很快就会放满了,一旦老年代放满了就会触发Full GC,但是,老年代GC过后,依然存活下来了很多的对象,这个时候如果年轻代还有一批对象等着放进老年代,就会发生OOM
    2. 系统有内存泄漏的问题,就是莫名其妙弄了很多的对象,结果对象都是存活的,没有及时取消对他们的引用,导致触发GC还是无法回收,此时只能引发内存溢出,因为内存实在放不下更多对象了
/**
 * @author 模拟堆内存溢出
-Xms5m
-Xmx5m
-XX:+PrintGCDetails
-Xloggc:gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
 * @date 2021 2021/9/8 16:31
 */
public class HeapOverFlow {
    public static void main(String[] args) {
        int count = 0;
        List<BigClass> list = new ArrayList<>();
        while (true) {
            list.add(new BigClass());
//            System.out.printf("创建第%d个对象\n", ++count);
        }
    }

    static class BigClass {
        byte[] bytes = new byte[1024];
    }
}

发生OOM处理思路

  1. 发生OOM了,必然说明系统中某个区域的对象太多了,塞满了那个区域,而且一定是无法回收掉那些对象,最终才会导致内存溢出的。
  2. 首先就得知道到底是什么对象太多了最终导致OOM的
  3. 在OOM时dump一份内存快照,事后分析这个内存快照,使用-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/usr/local/app/oom 两个参数设置。
第一个参数意思是在OOM的时候自动dump内存快照出来,第二个参数是说把内存快照放到哪儿去

  1. 使用MAT工具分析内存快照,查看是什么大对象占用的内存,进而分析该对象被谁引用。
  • MAT内存快照分析工具下载

https://www.eclipse.org/mat/downloads.php
image.png

  1. 栈内存溢出,只要把所有的异常都写入本地日志文件,那么当发现系统崩溃了,第一步就去日志里定位一下异常信息就知道了。
  • 查看大对象

image.png

  • 找到哪些线程创建了过多的对象

image.png

  • 找到占用内存最大的对象之后,最后一步就是要定位一下是哪一行代码,或者是哪个方法创建了那么多的对象

image.png

posted @ 2023-08-14 17:12  yangleduo114  阅读(178)  评论(0)    收藏  举报