Подтвердить что ты не робот

Что происходит, когда JVM прекращается?

Что происходит, когда JVM завершается с помощью System.exit(0) или ^C или чего-либо подобного? Я читаю такие вещи, как "процесс просто сдулся" и "каждый отдельный поток остановлен", но я хотел бы знать, что именно происходит. Я уже знаю, что есть shutdownHook который каким-то образом все еще выполняется, но что происходит до того, как shutdownHooks вызываются, и происходит ли что-нибудь после завершения всех этих потоков?

Я хотел бы правильно реализовать такой shutdownHook и для этого мне нужно сделать правильные предположения о том, что еще может быть выполнено, а что нет.


Обновить:

некоторый код:

class SomeObject {

    private boolean stopped;

    SomeObject() {
        stopped = false;
        Thread hook = new Thread() {

            @Override
            public void run() {
                stopped = true;
            }

        };
        hook.setPriority(Thread.MAX_PRIORITY);
        Runtime.getRuntime().addShutdownHook(hook);
    }

    boolean map(Iterator<Object> it) {
        while(it.hasNext() && !stopped) {
            writeToOtherObject(it.next());
            it.remove();
        }
        //have calculations finished?
        return !it.hasNext();
    }
}

Функция map вычисляет результаты, собранные в каком-то другом объекте. Этот объект должен храниться в каком-то файле до того, как все будет разбито (по обычному приоритету shutdownHook тоже). Имеет ли смысл shutdownHook здесь? Насколько я понимаю, все потоки сначала уничтожаются, и только после этого запускается shutdownHook (одновременно, но я предполагаю, что сначала запускаются потоки с высоким приоритетом...), а затем объекты завершаются. Это делает приведенный выше код довольно бесполезным, поскольку целью этого shutdownHook было бы убедиться, что новый цикл не запускается, когда завершение работы уже началось. Является ли мое понимание правильным и полным?

4b9b3361

Ответ 1

Давайте начнем с разных способов запуска последовательности выключения:

  • Последний конец не-демона заканчивается.
  • JVM прерывается (с помощью ctrl C или отправки SIGINT).
  • JVM завершается (путем отправки SIGTERM)
  • Один из потоков вызывает System.exit() или Runtime.exit().

Когда вызывается System.exit(int), он вызывает Runtime.exit(). Он проверяет с менеджером безопасности, разрешено ли ему выйти с данным статусом, и если да, вызовы Shutdown.exit().

Если вы прервали JVM или система отправила ему сигнал TERM, то по умолчанию Shutdown.exit() вызывается непосредственно без проверки с менеджером безопасности.

Класс Shutdown - это внутренний, пакетно-частный класс в java.lang. Он имеет, среди прочего, методы exit() и a halt(). Его метод exit() делает некоторые вещи, чтобы предотвратить выполнение крючков дважды и т.д., Но в основном, что он делает, это

  • Запустите системные клики. Системные крючки регистрируются внутри методом JRE. Они запускаются последовательно, а не в потоках. Второй системный крючок - это то, что запускает крючки приложения, которые вы добавили. Он запускает каждый из них как нить, а затем имеет join для каждого из них в конце. Другие системные перехватчики могут запускаться до или после того, как приложение перехватывает.
  • Если финализаторы должны быть запущены до остановки, они запускаются. Обычно этого не происходит, поскольку метод устарел. И если выход имеет статус, отличный от нуля, он все равно игнорирует runFinalizersOnExit.
  • JVM остановлен.

Теперь, вопреки вашему предположению, на третьем этапе все потоки остановлены. Метод halt является родным, и я не пытаюсь прочитать собственный код, но до момента его вызова единственный исполняемый код - это чистая Java, и ничто не останавливает нити в нем. В документации Runtime.addShutdownHook говорится:

Крюк отключения - это просто инициализированный, но нерасширенный поток. Когда виртуальная машина начнет свою последовательность выключения, она запустит все зарегистрированные крючки отключения в некотором неуказанном порядке и позволит им запускать одновременно. Когда все крючки закончены, он будет запускать все неинвинированные финализаторы, если финализация на выходе будет включена. Наконец, виртуальная машина остановится. Обратите внимание, что потоки демона будут продолжать выполняться во время последовательности выключения, а также потоки не-демона, если выключение было инициировано вызовом метода exit.

(акцент мой)

Итак, вы видите, что на самом деле это часть задания на работу при завершении работы, чтобы сообщить потокам, что они должны оставить свои петли и очистить.

Другое неправильное представление о том, что вы придаете потоку высокий приоритет. Высокий приоритет не означает, что поток будет запускаться первым, перед всеми другими крючками. Это просто означает, что всякий раз, когда операционная система должна принять решение о том, какие из потоков, которые находятся в состоянии "готов к запуску", чтобы дать процессору работать, высокоприоритетный поток будет иметь более высокую вероятность "выигрыша" - в зависимости в алгоритме планирования операционной системы. Короче говоря, он может получить немного больше доступа к ЦП, но он не будет - особенно если у вас есть более одного ядра процессора - обязательно запускается перед другими потоками или завершается перед ними.

Последнее: если вы хотите использовать флаг, чтобы указать потоку прекратить работу, этот флаг должен быть volatile.

Ответ 2

Взгляните на article из DZone. Он достаточно подробно описывает JVM и его завершение с упором на использование ShutdownHook.

На высоком уровне некоторые важные предположения, которые он охватывает, включают в себя:

  • Завершение работы Крючки могут не выполняться в некоторых случаях!
  • После запуска Крюки выключения могут быть принудительно остановлены до завершения.
  • У вас может быть несколько выключений, но их исполнение не гарантируется.
  • Вы не можете регистрировать/отменить регистрацию "Завершение работы" с помощью "Завершение работы"
  • После запуска последовательности выключения его можно остановить только с помощью Runtime.halt().
  • Использование прерываний для завершения работы требует разрешения безопасности.
  • Исключения, вызванные Крюками Завершения, обрабатываются так же, как исключения, брошенные любым другим сегментом кода.