Почему эта небольшая 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-ядерные процессоры с большими кэшами (например, 12 МБ).

Несмотря на то, что параллельная обработка сильно эволюционировала, Java по-прежнему разработана на основе модели одноядерного процессора. Но, хватит истории, позвольте мне очень просто объяснить, что происходит.

Просто создавая новый поток в Java, мы тратим много памяти.

Каждый поток занимает около 512 КБ - 1 МБ, в зависимости от вашей версии JVM (посмотрите, сколько памяти занимает поток в java и Java Thread: сохраненная память). Принимая это во внимание, при непрерывном создании новых потоков в какой-то момент они будут использовать всю память кучи.

Теперь я никогда не пробовал самостоятельно, но я полагаю, что операционная система вашего компьютера выключается/перезагружается из-за ошибки "недостаточно памяти" в качестве контрмеры. (Это очень похоже на тройной сбой, который вызвал печально известный "синий экран смерти" в Windows, где машина должна была перезагрузиться, чтобы сбросить состояние процессора)

Одним из возможных решений для этого является ручная установка максимального размера кучи, который будет использоваться JVM. Таким образом, когда ваша программа полностью использует предварительно выделенную кучу, это не приведет к завершению работы. Пожалуйста, обратитесь к такому вопросу о том, как это сделать.

Ответ 3

Это вариант бомбы Форк. Это может привести к серьезному замедлению работы, но ни одна пользовательская программа не должна быть способна аварийно завершить работу ОС. Это, вероятно, ошибка в ОС или ошибка памяти. Попробуйте запустить проверку памяти?

Ответ 4

Скорее всего, потому, что вы либо не предоставили JVM достаточно памяти, либо комбинация аппаратного обеспечения компьютера и macOS не позволяет одновременно активировать столько потоков. Эта проблема не ограничивается macOS, но некоторые дистрибутивы Linux, например Bodhi Linux, также имеют это ограничение. Не обманывайтесь "OutOfMemoryError" - это часто может означать, что JVM не смогла выделить собственный поток.