为什么这个小的Java程序会让MacOS重新启动?

代码如下

Set<Thread> threads = new HashSet<>();

Runnable r = () -> {
    try {
        Thread.sleep(Long.MAX_VALUE);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

for (int i = 0; i < 20000; i++) {
    Thread t = new Thread(r);
    threads.add(t);
    t.start();
    if (i % 100 == 0) {
        System.out.println(i);
    }
    Thread.sleep(2);
}

执行时,我开始看到这样的值

0
100
200
300

正如预期的那样,它一直持续到我看到:

3900
4000
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at App.main(scratch.java:24)
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

但过了一会儿(10-20秒左右),MacOS决定重新启动。我在这里看到的重新启动的原因是什么?主线程抛出异常,但具有约4000个线程的进程休眠导致...操作系统中的什么?这是内存溢出还是与操作系统的任务计划程序有关?

MacOS version: 10.14.3 (18D109)
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

答案 1

尽管控制台显示程序已完成,但 JVM 进程仍在运行,直到释放所有资源。同时,您的操作系统线程不足,速度慢且不稳定,这会导致所有进程(包括JVM终结)滞后。作为自我防御,操作系统会触发内核崩溃。这就是您的MacOS重新启动的原因。

*操作系统 - 操作系统


答案 2

Java是在90年代构建的,当时只有多核处理器。

当然,Java已经发展,现代处理器也是如此。如今,我们有8核处理器,具有大型缓存(例如:12MB)。

尽管并发处理已经发展了很多,但Java仍然是围绕1核处理器模型设计的。但是,历史已经足够了,让我非常非常简单地解释发生了什么。

仅仅通过在Java中简单地创建一个新线程,我们就会浪费大量的内存。

每个线程消耗大约 512KB - 1MB,具体取决于您的 JVM 版本(请参阅线程在 java 和 Java 线程中占用多少内存:保留内存)。考虑到这一点,当不断创建新线程时,它们将在某个时候消耗堆的所有内存。

现在,我自己从未尝试过此操作,但是作为对策,我假设您的计算机的操作系统由于“内存不足”错误而关闭/重新启动。(这很像三重故障,导致Windows上臭名昭着的“蓝屏死机”,机器需要重新启动才能重置CPU的状态)

一种可能的解决方案是手动设置 JVM 要使用的最大堆大小。因此,当程序完全利用预分配的堆时,它不会导致关闭。请参阅SO问题,了解如何执行此操作。