面试官:发生OOM后,JVM还能运行吗?

本文首发于公众号:托尼学长,立个写 1024 篇原创技术面试文章的flag,欢迎过来视察监督~

这是一道非常有意思的面试题,虽然不是特别高频,但也时不时的也会有面试官问起来。

而95%候选人给出的答案都是错的,他们认为"发生OOM后,JVM也一定不能运行了",其实结果恰恰相反。

下面我们直接Show Me The Code,一起进行场景复现。

双子线程场景

首先,我们将IDEA中的最大内存数设置成16M,以便于我们后续进行代码实验。

然后我们先运行一段代码,看看设置是否生效。

java 复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestOOM {

    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<byte[]>();
        for (;;) {
            System.out.println(new Date().toString() + " OOM Thread is running!!!");
            byte[] b = new byte[1024 * 1024 * 1];
            list.add(b);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

果然生效了,代码没运行多久就报堆内存溢出了。

接下来我们在代码中模拟双子线程场景,看看到底会出现什么情况。

java 复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestOOM {

    public static void main(String[] args) {

        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread is running!!!");
                byte[] b = new byte[1024 * 1024 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            for (;;) {
                System.out.println(new Date().toString() + " Other Thread is running!!!");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

如日志所示,当一个线程已经出现OOM的时候,另一个线程仍在继续运行着。

从jconsole工具中可以看到,堆内存占用随着一个线程中的ArrayList不断填充对象而升高,高到xmx16M阈值的时候发生OOM并急转直下,随后趋于平稳。

这说明一个线程导致OOM的时候,该线程会终止运行并清空其所占用的内存资源,保证其他线程可以继续正常运行。

其实这种情况也是合理的,以现实生活为例,当写字楼电梯超载的时候,一定会让体重最重的那个胖子走下电梯,电梯还是要继续运行的。

主子线程场景

我们再来试试主子线程的场景,代码如下:

java 复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestOOM {

    public static void main(String[] args) {

        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread is running!!!");
                byte[] b = new byte[1024 * 1024 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        for (;;) {
            System.out.println(new Date().toString() + " Master Thread is running!!!");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

如日志所示,当子线程已经出现OOM的时候,该线程会终止运行并清空其所占用的内存资源,主线程并不会被影响,还是再持续运行着。

OOM线程判定

我们来思考一个比较有意思的问题,如果两个子线程都往ArrayList中存放数据,那如何判定是哪个线程导致的OOM呢?接下来继续用代码进行实现:

java 复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestOOM {

    public static void main(String[] args) {

        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread1 is running!!!");
                byte[] b = new byte[1024 * 1024 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread2 is running!!!");
                byte[] b = new byte[1024 * 1024 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
java 复制代码
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class TestOOM {
    public static void main(String[] args) {
        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread1 is running!!!");
                byte[] b = new byte[1024 * 1024 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            List<byte[]> list = new ArrayList<byte[]>();
            for (;;) {
                System.out.println(new Date().toString() + " OOM Thread2 is running!!!");
                byte[] b = new byte[1024 * 512 * 1];
                list.add(b);
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

我们把这段代码反复执行,发现一个挺有趣的现象,线程1导致的OOM次数明显多一些,线程2导致的OOM次数少一些。随后我们把线程2往ArrayList中放512KB数据,改为了仅放128KB数据,线程2导致的OOM次数就更少了。真相原来是这样,哪个线程最后往ArrayList中存放数据导致OOM,成为压死骆驼的最后一根稻草,哪个线程就会被终止执行并清空其所占用的内存资源。

毕竟往ArrayList中写128KB数据导致最终OOM概率,远比往ArrayList中写1M数据最终导致OOM概率低很多。这样看来,还是Linux的OOM killer(Out Of Memory killer)机制更加智能一些,它是通过终止占用内存最多的进程来保障系统稳定运行的。

相关推荐
无处不在的海贼5 小时前
小明的Java面试奇遇之发票系统相关深度实战挑战
java·经验分享·面试
武子康5 小时前
Java-109 深入浅出 MySQL MHA主从故障切换机制详解 高可用终极方案
java·数据库·后端·mysql·性能优化·架构·系统架构
秋难降5 小时前
代码界的 “建筑师”:建造者模式,让复杂对象构建井然有序
java·后端·设计模式
孤雪心殇5 小时前
如何安全,高效,优雅的提升linux的glibc版本
linux·后端·golang·glibc
BillKu6 小时前
Spring Boot 多环境配置
java·spring boot·后端
new_daimond6 小时前
Spring Boot项目集成日志系统使用完整指南
spring boot·后端
哈基米喜欢哈哈哈7 小时前
Kafka复制机制
笔记·分布式·后端·kafka
君不见,青丝成雪7 小时前
SpringBoot项目占用内存优化
java·spring boot·后端
追逐时光者8 小时前
一个 .NET 开源、功能强大的在线文档编辑器,类似于 Microsoft Word,支持信创!
后端·.net