最近有人在微信群里问我,定时器 OOM(java.lang.OutOfMemoryError: Java heap space)了,其他功能还正常吗?
说实话我之前在浦发的时候,也有遇到过。一个同事写了一个定时备份数据的功能,有一次做活动导致数据量增了好几倍,备份的时候定时任务发生了 OOM,导致数据没有备份成功。这个问题直到第二天才被发现,因为 OOM 后,其他功能都正常,业务都没发现。
第二天是怎么发现的呢?因为第二天业务发现定时发短信的定时任务不执行了,于是大家一顿排查发现系统在昨天就已经 OOM 了。现在影响扩大了,导致其他功能不可用,进而蔓延到整个系统。
在这个过程中,我们先不说这个备份合不合理,我们从 JVM 内存结构来说说为什么一个线程 OOM 了,其他线程不受影响。
这个不受影响,大家别误会。一个线程 OOM 后,其他线程是可以正常运行的,但是内存泄露之后进而会导致整个程序内存溢出,最终程序不可用。
那么我们下面说一下,一个线程 OOM 了,为什么其他线程不受影响呢?
要回答这个问题,我们先来回想一下 java 的内存结构。如下图所示:
我们知道,多线程的时候,每个线程都拥有一个栈和一个程序计数器。栈和程序计数器用来保存线程的执行历史和线程的执行状态,是线程私有的资源。堆是线程共享的,所以理论上一个线程 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 的,可以查看我的这篇文章:使用VisualVM对JAVA程序进行性能分析及调优。
根据上图,我们可以得出当一个线程抛出 OOM 异常后,它所占据的内存资源会被快速的释放掉,从而不会影响其他线程的运行!
另外当一个线程 OutOfMemoryError 后,如果这个 OutOfMemoryError 被捕获,那么 catch 之后吞掉的话程序还能试着继续运行。发生 OutOfMemoryError 之后,只是当前这个线程申请更多的内存的时候不被 JVM 允许,所以会抛出 OutOfMemoryError 异常。当抛出 OutOfMemoryError 异常后,当前这个线程会被退出,它所占的内存会被 JVM 清理掉。
那么 JVM 为什么要这么设计呢?
答案是,Java 程序通常不是为了适应意外的异常而设计的,OOM 之后可能导致应用状态不一致,建议最好重启。
参考资料
- Can the JVM recover from an OutOfMemoryError without a restart
- JAVA发生OOM后还能运行么?
: » 定时器 OOM(OutOfMemoryError) 了,其他线程受影响吗?
原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/251861.html