面试官:发生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)机制更加智能一些,它是通过终止占用内存最多的进程来保障系统稳定运行的。

相关推荐
nice_lcj5201 小时前
Java Lambda 表达式入门:怎么写、怎么用、常用方法有哪些
java·开发语言·后端·面试
IT_陈寒2 小时前
Redis大key删除为什么会导致服务卡顿?我的线上事故复盘
前端·人工智能·后端
系统集成-鹰翔2 小时前
ERP图片改名后仍命中识别缓存:缓存键、十分钟到期与调用计数
javascript·经验分享·后端
用户8356290780512 小时前
使用 Python 在 Excel 中添加和自定义迷你图
后端·python
码事漫谈2 小时前
给AI看一颗❤️,它就开始写递归了
后端
用户8356290780512 小时前
使用 Python 在 Excel 图表中添加趋势线与回归方程
后端·python
你三大爷3 小时前
Netty / Kafka / Redis 保序问题的源码对比与 NIO 框架主流解法
后端·架构
特立独行的猫A3 小时前
一份 Markdown 发十个平台:cspub 一稿多投
java·后端·python
3DGISer3 小时前
不透明物体的渲染算法
后端
ServBay3 小时前
从代码到上线,Cursor Live Apps 能替独立开发者做什么
后端·ai编程·cursor