Когда мы должны вызывать System.exit в Java

193

В Java, в чем разница с или без System.exit(0)в следующем коде?

public class TestExit
{      
    public static void main(String[] args)
    { 
        System.out.println("hello world");

        System.exit(0);  // is it necessary? And when it must be called? 
    }      
}

В документе говорится: «Этот метод никогда не возвращается нормально». Что это означает?

pierrotlefou
источник

Ответы:

207

System.exit()может использоваться для запуска перехватчиков до выхода из программы. Это удобный способ для завершения работы в больших программах, где все части программы не могут (и не должны) знать друг о друге. Затем, если кто-то хочет выйти из системы, он может просто позвонить System.exit(), а перехватчики завершения работы (если они правильно настроены) позаботятся о выполнении всех необходимых церемоний отключения, таких как закрытие файлов, освобождение ресурсов и т. Д.

«Этот метод никогда не возвращается нормально». просто означает, что метод не вернется; как только нить идет туда, она не вернется.

Другой, может быть, более распространенный способ выхода из программы - просто достичь конца mainметода. Но если запущены какие-либо потоки, не являющиеся демонами, они не будут закрыты, и, следовательно, JVM не выйдет. Таким образом, если у вас есть такие потоки, не являющиеся демонами, вам нужны другие средства (кроме ловушек завершения), чтобы закрыть все потоки, не являющиеся демонами, и освободить другие ресурсы. Если нет других потоков, не являющихся демонами, возврат из него mainотключит JVM и вызовет перехватчики завершения работы.

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

Joonas Pulakka
источник
10
«Этот метод никогда не возвращается нормально». просто означает, что метод не вернется; как только нить идет туда, она не вернется. В частности, обратите внимание, что это означает, что вы не можете выполнить модульное тестирование метода, который вызывает System.exit (0) ...
Билл Мичелл,
5
-1 Неверно. Завершение работы завершается, если JVM завершает работу нормально, независимо от того, происходит ли это из-за System.exit или завершения main (). См. Zx81 / doku / java / javadoc / j2se1.5.0 / docs / api / java / lang /…
sleske
31
@sleske: Прекращение main () недостаточно, если вокруг есть другие не-демонные потоки. Завершение работы инициируется только после завершения последнего потока, не являющегося демоном, если вы явно не вызываете System.exit (). Это четко указано в документации по времени выполнения.
Joonas Pulakka
3
Обратите внимание, что если ваш хук завершения работы в свою очередь зависит от потока, который называется System.exit, вы будете в тупике.
Джечлин
8
Что-то, что можно добавить, если кто-то остановит среду выполнения, перехватчики завершения не будут работать ( Runtime.getRuntime().halt())
Rogue
50

В этом случае это не нужно. Никаких дополнительных потоков не будет запущено, вы не меняете код выхода (по умолчанию 0) - в основном это бессмысленно.

Когда в документах говорится, что метод никогда не возвращается нормально, это означает, что следующая строка кода фактически недоступна, даже если компилятор этого не знает:

System.exit(0);
System.out.println("This line will never be reached");

Либо будет выдано исключение, либо виртуальная машина прекратит работу перед возвратом. Он никогда не "просто вернется".

Очень редко стоит звонить в System.exit()IME. Это может иметь смысл, если вы пишете инструмент командной строки и хотите указать ошибку с помощью кода выхода, а не просто выбросить исключение ... но я не могу вспомнить, когда в последний раз я использовал его в обычном рабочем коде ,

Джон Скит
источник
2
Почему компилятор этого не знает? Разве System.exit () не является достаточно специальным, чтобы гарантировать определенный код обнаружения?
Барт ван Хейкелом
3
@ Барт: Нет, я так не думаю. Помещение специальных языков в язык для подобных вещей увеличивает сложность языка с очень небольшой выгодой.
Джон Скит
Я думал, что "никогда не возвращается нормально" было связано с "внезапным завершением" утверждения. (JLS раздел 14.1). Я ошибся?
2010 г.,
@aioobe: Нет, ты прав. System.exit()никогда не завершится нормально - он всегда завершится либо с исключением, либо с выключением виртуальной машины (что на самом деле не рассматривается в JLS).
Джон Скит
1
@piechuckerr: Что заставляет тебя так думать? Вполне разумно вызывать его со значением, отличным от 0, чтобы указать вызывающей оболочке (или любой другой), что программа обнаружила ошибку.
Джон Скит
15

System.exit(0)завершает JVM. В таких простых примерах трудно понять разницу. Параметр передается обратно в ОС и обычно используется для указания аварийного завершения (например, какой-то фатальной ошибки), поэтому, если вы вызвали java из командного файла или сценария оболочки, вы сможете получить это значение и получить представление если заявка была успешной.

Это оказало бы большое влияние, если бы вы вызвали System.exit(0)приложение, развернутое на сервере приложений (подумайте об этом, прежде чем попробовать).

Qwerky
источник
15

Метод никогда не возвращается, потому что это конец света, и следующий код не будет выполнен.

Ваше приложение в вашем примере будет в любом случае завершать работу в том же месте кода, но, если вы используете System.exit. у вас есть возможность вернуть пользовательский код в среду, например, скажем,

System.exit(42);

Кто будет использовать ваш код выхода? Скрипт, который вызывает приложение. Работает в Windows, Unix и других скриптовых средах.

Зачем возвращать код? Сказать что-то вроде «Мне не удалось», «База данных не ответила».

Чтобы узнать, как получить значение кода выхода и использовать его в сценарии оболочки Unix или сценарии Windows cmd, вы можете проверить этот ответ на этом сайте.

Мико
источник
12

В приложениях, которые могут иметь сложные ловушки завершения работы, этот метод не должен вызываться из неизвестного потока. System.exitникогда не завершается нормально, потому что вызов будет заблокирован до завершения JVM. Это похоже на то, что любой код, на котором выполняется отключение питания, прежде чем он сможет завершить работу. Вызов System.exitбудет инициировать перехватчики завершения программы и любой поток, который вызывает, System.exitбудет блокироваться до завершения программы. Это подразумевает, что, если хук завершения работы, в свою очередь, отправляет задачу потоку, из которого System.exitбыл вызван, программа будет заблокирована.

Я обрабатываю это в моем коде с помощью следующего:

public static void exit(final int status) {
    new Thread("App-exit") {
        @Override
        public void run() {
            System.exit(status);
        }
    }.start();
}
djechlin
источник
У меня также была проблема, когда System.exit фактически не заканчивал JVM. Это происходит только при определенных условиях. Поток нити, который я использовал, не дал мне никакого представления о том, какие потоки / обработчики выключения блокируют завершение работы. Использование кода, описанного в комментарии, помогло мне решить эту проблему!
Кай
7

Хотя ответ был действительно полезным, но кое-как он упустил некоторые дополнительные детали. Я надеюсь, что ниже поможет понять процесс завершения работы в Java, в дополнение к ответу выше:

  1. При правильном * отключении JVM сначала запускает все зарегистрированные прерывания отключения. Завершение работы - это незапущенные потоки, которые зарегистрированы в Runtime.addShutdownHook.
  2. JVM не дает никаких гарантий относительно порядка запуска отключающих крюков. Если какие-либо потоки приложения (daemon или nondeemon) все еще работают во время завершения работы, они продолжают работать одновременно с процессом завершения работы .
  3. Когда все перехватчики завершения завершены, JVM может выбрать запуск финализаторов, если runFinalizersOnExit имеет значение true, и затем останавливается.
  4. JVM не пытается останавливать или прерывать какие-либо потоки приложений, которые все еще работают во время завершения работы; они внезапно прекращаются, когда JVM в конце концов останавливается.
  5. Если перехватчики завершения работы или финализаторы не завершаются, то процесс упорядоченного завершения работы «зависает», и JVM должна быть внезапно закрыта.
  6. При внезапном отключении JVM не требуется делать что-либо кроме остановки JVM; отключающие крючки не будут работать.

PS: JVM может отключиться упорядоченным или резким образом.

  1. Упорядоченное завершение запускается, когда завершается последний «нормальный» (не-демон) поток, кто-то вызывает System.exit или другими специфическими для платформы средствами (такими как отправка SIGINT или нажатие Ctrl-C).
  2. Хотя вышеуказанное является стандартным и предпочтительным способом завершения работы JVM, его также можно внезапно отключить, вызвав Runtime.halt или убив процесс JVM через операционную систему (например, отправив SIGKILL).
Джон
источник
7

НИКОГДА не следует звонить System.exit(0)по этим причинам:

  1. Это скрытое «goto» и «gotos» нарушают поток управления. В этом контексте опора на ловушки - это ментальное отображение, о котором должен знать каждый разработчик в команде.
  2. Выход из программы «обычно» предоставляет тот же код выхода для операционной системы, что System.exit(0)и является избыточным.

    Если ваша программа не может выйти «нормально», вы потеряли контроль над своей разработкой [дизайном]. Вы должны всегда иметь полный контроль над состоянием системы.

  3. Проблемы программирования, такие как запуск потоков, которые не были остановлены, обычно становятся скрытыми.
  4. Вы можете столкнуться с несогласованным состоянием приложения, которое прерывает потоки ненормально. (См. № 3)

Кстати: возврат кодов возврата, отличных от 0, имеет смысл, если вы хотите указать на ненормальное завершение программы.

oopexpert
источник
2
«Вы должны всегда иметь полный контроль над состоянием системы». Это просто невозможно в Java. Платформы IoC и серверы приложений - это всего лишь два очень популярных и широко используемых способа отказа от контроля над состоянием системы. И это совершенно нормально.
Mhlz
Пожалуйста, запустите System.exit (0) на вашем сервере приложений и расскажите мне о реакции администратора вашего сервера ... Вы пропустили тему этого вопроса и мой ответ.
oopexpert
Может быть, я не был достаточно ясен. Вы должны всегда иметь полный контроль над состоянием системы, за которое несете ответственность. Да, некоторые обязанности были перенесены на серверы приложений и IoC. Но я не говорил об этом.
oopexpert
5

Нужен System.exit

  • когда вы хотите вернуть код ошибки, отличный от 0
  • когда вы хотите выйти из вашей программы не из main ()

В вашем случае он делает то же самое, что и простой возврат из основного.

MFX
источник
Что вы имеете в виду с последним? Правильный способ закрыть программу - это остановить все потоки (приятно), шаг, который не должен быть инициирован из основного потока, так что это не похоже exitна ваш единственный вариант.
Барт ван Хейкелом
@ Барт: то, что вы описываете, является одним из возможных способов закрыть программу, а не правильным способом . Нет ничего плохого в том, чтобы с помощью перехватчиков завершить все потоки. И отключаются крюки с exit. Не единственный вариант, но определенно стоит рассмотреть вариант.
Joonas Pulakka
Но то, что я собираю из документов, крюки отключения очень деликатны и могут не иметь много времени, чтобы делать то, что вы хотите, чтобы они делали. Во что бы то ни стало, используйте их для приятного выхода в случае выключения ОС или чего-то еще, но если вы собираетесь вызывать System.exit () самостоятельно, я думаю, что лучше сделать код выключения перед ним (после чего вы, вероятно, не будете больше не нужен System.exit ())
Барт ван Хейкелом
1
У крюков отключения есть все время, которое они берут, чтобы закончить. ВМ не останавливается до того, как вернутся все хуки. Действительно возможно предотвратить выход виртуальной машины, зарегистрировав перехват выключения, выполнение которого занимает очень много времени. Но, конечно, можно выполнить последовательность выключения различными, альтернативными способами.
Joonas Pulakka
Возврат из main не завершится, если есть другие потоки, не являющиеся демонами.
Джечлин
4

Спецификация языка Java говорит, что

Выход из программы

Программа завершает всю свою деятельность и завершается, когда происходит одно из двух:

Все потоки, которые не являются потоками демона, завершаются.

Некоторые потоки вызывают метод выхода класса Runtime или класса System , и операция выхода не запрещена менеджером безопасности.

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

Марчин Шимчак
источник
3

Если в JVM запущена другая программа и вы используете System.exit, эта вторая программа также будет закрыта. Представьте, например, что вы выполняете задание Java на узле кластера и что Java-программа, которая управляет узлом кластера, выполняется в той же JVM. Если задание будет использовать System.exit, оно не только завершит задание, но и «завершит работу всего узла». Вы не сможете отправить другое задание на этот узел кластера, поскольку программа управления была случайно закрыта.

Поэтому не используйте System.exit, если вы хотите иметь возможность управлять своей программой из другой Java-программы в той же JVM.

Используйте System.exit, если вы хотите нарочно закрыть полную JVM и если вы хотите воспользоваться возможностями, описанными в других ответах (например, перехватчики завершения работы : перехват завершения работы Java , ненулевое возвращаемое значение для командной строки). звонки: Как получить статус выхода Java-программы в пакетном файле Windows ).

Также взгляните на исключения времени выполнения: System.exit (num) или выведите исключение времени выполнения из main?

Стефан
источник