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

相关推荐
计算机学姐1 分钟前
基于SpringBoot+Vue的家政服务预约系统【个性化推荐+数据可视化】
java·vue.js·spring boot·后端·mysql·信息可视化·java-ee
智能工业品检测-奇妙智能2 分钟前
Ubuntu24安装mysql8
人工智能·spring boot·后端·openclaw·奇妙智能
2301_793804693 分钟前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
森林里的程序猿猿5 分钟前
导致内存泄漏的ThreadLocal详解
java·jvm·数据结构
Dream_sky分享12 分钟前
Excel模板下载(Resources目录下)
java·spring boot·后端
羊小猪~~12 分钟前
算法/力扣--链表经典题目
数据结构·后端·考研·算法·leetcode·链表·面试
Anastasiozzzz13 分钟前
编程语言错误处理的清流:Go 错误处理
开发语言·后端·golang
A_nanda19 分钟前
C#类型转换最佳实践
java·jvm·c#
Moment19 分钟前
手把手搭一套前端监控采集 SDK
前端·javascript·面试
木易 士心19 分钟前
自然语言转数据库操作语句原理架构图分析和实现
数据库·后端