定时器 OOM(OutOfMemoryError) 了,其他线程受影响吗?

/ Java / 没有评论 / 1844浏览

定时器 OOM(OutOfMemoryError) 了,其他线程受影响吗?

最近有人在微信群里问我,定时器 OOM(java.lang.OutOfMemoryError: Java heap space)了,其他功能还正常吗?

说实话我之前在浦发的时候,也有遇到过。一个同事写了一个定时备份数据的功能,有一次做活动导致数据量增了好几倍,备份的时候定时任务发生了 OOM,导致数据没有备份成功。这个问题直到第二天才被发现,因为 OOM 后,其他功能都正常,业务都没发现。第二天是怎么发现的呢?因为第二天业务发现定时发短信的定时任务不执行了,于是大家一顿排查发现系统在昨天就已经 OOM 了。现在影响扩大了,导致其他功能不可用,进而蔓延到整个系统。

在这个过程中,我们先不说这个备份合不合理,我们从 JVM 内存结构来说说为什么一个线程 OOM 了,其他线程不受影响。这个不受影响,大家别误会。一个线程 OOM 后,其他线程是可以正常运行的,但是内存泄露之后进而会导致整个程序内存溢出,最终程序不可用。那么我们下面说一下,一个线程 OOM 了,为什么其他线程不受影响呢?

要回答这个问题,我们先来回想一下 java 的内存结构。如下图所示:

JVM 内存结构

我们知道,多线程的时候,每个线程都拥有一个栈和一个程序计数器。栈和程序计数器用来保存线程的执行历史和线程的执行状态,是线程私有的资源。堆是线程共享的,所以理论上一个线程 OOM 了,其他线程应该受影响才对啊,实际上却并不是,这是什么原因呢?

有兴趣的可以按照我下面的这段代码自己去跑一下,测试一下这个内存溢出。

public class HeapOutOfMemoryError {
    //业余草:www.xttblog.com
    public static class OOMObject {}
    public static void main(String[] args) {
        new Thread(() -> {
            while(true){
                System.out.println(new Date().toString() + Thread.currentThread() + "www.xttblog.com");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            List<Object> list=new ArrayList<>();
            // 不断创建对象,并保证GC Roots到对象之间有可达路径,避免垃圾回收清除创建的对象
            while (true) {
                list.add(new OOMObject());
                System.out.println(System.currentTimeMillis());
            }
        }).start();
    }  
}

再测试的时候,可以将内存设置的很小,便于重现。

-Xms1m -Xmx2m

-Xms 初始堆内存 -Xmx 最大堆内存

然后结合 JvisualVM 工具,你会看到,在程序内存溢出之后,溢出的内存的线程所占的内存会被快速释放。如下图所示:

JvisualVM

不会 JvisualVM 的,可以查看我的这篇文章:使用VisualVM对JAVA程序进行性能分析及调优

根据上图,我们可以得出当一个线程抛出 OOM 异常后,它所占据的内存资源会被快速的释放掉,从而不会影响其他线程的运行!

另外当一个线程 OutOfMemoryError 后,如果这个 OutOfMemoryError 被捕获,那么 catch 之后吞掉的话程序还能试着继续运行。发生 OutOfMemoryError 之后,只是当前这个线程申请更多的内存的时候不被 JVM 允许,所以会抛出 OutOfMemoryError 异常。当抛出 OutOfMemoryError 异常后,当前这个线程会被退出,它所占的内存会被 JVM 清理掉。

那么 JVM 为什么要这么设计呢?

答案是,Java 程序通常不是为了适应意外的异常而设计的,OOM 之后可能导致应用状态不一致,建议最好重启。

参考资料