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

相关推荐
BYSJMG几秒前
计算机毕业设计选题推荐:基于Hadoop的城市交通数据可视化系统
大数据·vue.js·hadoop·分布式·后端·信息可视化·课程设计
独自破碎E1 分钟前
字符串相乘
android·java·jvm
jiayong233 分钟前
Vue2 与 Vue3 常见面试题精选 - 综合宝典
前端·vue.js·面试
BYSJMG10 分钟前
Python毕业设计选题推荐:基于大数据的美食数据分析与可视化系统实战
大数据·vue.js·后端·python·数据分析·课程设计·美食
东东51619 分钟前
OA自动化居家办公管理系统 ssm+vue
java·前端·vue.js·后端·毕业设计·毕设
程序员鱼皮34 分钟前
前特斯拉 AI 总监:AI 编程最大的谎言,是 “提效”
前端·后端·ai·程序员·开发
2301_8223754436 分钟前
Python虚拟环境(venv)完全指南:隔离项目依赖
jvm·数据库·python
2301_7903009637 分钟前
Python类型提示(Type Hints)详解
jvm·数据库·python
chilavert3181 小时前
技术演进中的开发沉思-349:高效并发(下)
java·jvm
2401_838472511 小时前
Python多线程与多进程:如何选择?(GIL全局解释器锁详解)
jvm·数据库·python