定时器 OOM(OutOfMemoryError) 了,其他线程受影响吗?
最近有人在微信群里问我,定时器 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 之后可能导致应用状态不一致,建议最好重启。
参考资料
本文由 创作,采用 知识共享署名4.0 国际许可协议进行许可。本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为: 2021/04/21 03:59