Что может заставить Java продолжать работать после System.exit()?
У меня есть Java-программа, которая запускается через ProcessBuilder
из другой программы Java.
System.exit(0)
вызывается из дочерней программы, но для некоторых наших пользователей (в Windows) процесс java.exe
, связанный с дочерним элементом, не завершается. У дочерней программы нет завершающих крючков, и у нее нет SecurityManager
, которая может остановить System.exit()
от завершения виртуальной машины. Я не могу воспроизвести проблему самостоятельно в Linux или Windows Vista. До сих пор единственными сообщениями о проблеме стали два пользователя Windows XP и один пользователь Vista, используя два разных JRE (1.6.0_15 и 1.6.0_18), но каждый раз они могут воспроизводить проблему.
Может кто-нибудь предложить причины, по которым JVM не завершится после System.exit()
, а затем только на некоторых машинах?
Изменить 1: Я получил пользователя, чтобы установить JDK, чтобы мы могли получить дамп потока от нарушительной виртуальной машины. Мне сказали, что процесс VM исчезает из VisualVM, как только он нажимает на элемент "Выход" в моем меню --- но, согласно диспетчеру задач Windows, процесс не прекращается и не зависит от того, как долго пользователь ждет (минуты, часы), он никогда не заканчивается.
Изменить 2: Я подтвердил, что Process.waitFor()
в родительской программе никогда не возвращается для хотя бы одного из пользователей, имеющих проблему. Итак, суммируем: дочерняя VM кажется мертвой (VisualVM ее даже не видит), но родитель все еще видит процесс как живой, а также делает Windows.
Ответы
Ответ 1
Родительский процесс имеет один поток посвященный потреблению каждого из ребенок STDOUT и STDERR (который передает этот вывод в журнал файл). Насколько я вижу, это работаем правильно, поскольку мы видим весь результат, который мы ожидаем увидеть в Журнал
У меня была аналогичная проблема с тем, что моя программа не исчезла из задачи mgr, когда я потреблял stdout/stderr. в моем случае, если я закрыл поток, который слушал, прежде чем вызвать system.exit(), тогда javaw.exe повесился. странно, он не писал в поток...
решение в моем случае состояло в том, чтобы просто очистить поток, а не закрыть его до существующего. конечно, вы всегда можете сбросить, а затем перенаправить обратно в stdout и stderr перед выходом.
Ответ 2
Это может произойти, если ваш код (или используемая вами библиотека) имеет завершающий или завершающий финализатор, который не заканчивается чисто.
Более энергичный (поэтому его следует использовать только в крайних случаях!), чтобы заставить отключить работу:
Runtime.getRuntime().halt(0);
Ответ 3
Вот несколько сценариев...
В определении потока в http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Thread.html
...
Когда запускается виртуальная машина Java, обычно существует один поток не-демона (который обычно вызывает метод с именем main определенного класса). Виртуальная машина Java продолжает выполнять потоки, пока не произойдет одно из следующих действий:
1) Вызывается метод выхода класса Runtime, и менеджер безопасности разрешил операцию выхода.
2) Все потоки, которые не являются потоками демона, умерли, либо возвратившись из вызова к методу запуска, либо выбросив исключение, которое распространяется за пределы метода run.
Другая возможность заключается в вызове метода runFinalizersOnExit. согласно документации в http://java.sun.com/j2se/1.4.2/docs/api/java/lang/System.html
Устаревшее. Этот метод по своей сути является небезопасным. Это может привести к тому, что финализаторы будут вызываться на живых объектах, в то время как другие потоки одновременно манипулируют этими объектами, что приводит к неустойчивому поведению или взаимоблокировке.
Включить или отключить финализацию при выходе; это указывает, что финализаторы всех объектов, у которых есть финализаторы, которые еще не были автоматически вызваны, должны выполняться до завершения выполнения Java-среды. По умолчанию финализация при выходе отключена.
Если есть менеджер безопасности, его метод checkExit сначала вызывается с 0 в качестве аргумента, чтобы обеспечить разрешение выхода. Это может привести к возникновению SecurityException.
Ответ 4
Может быть, плохо написанный финализатор? Когда я прочитал сюжетную линию, моя первая мысль была остановлена. Спекуляция: будет ли поток, который улавливает InterruptedException и продолжает работать в любом случае, удерживает процесс выхода?
Мне кажется, что если проблема воспроизводима, вы сможете подключиться к JVM и получить трассировку списка/стека потоков, которая показывает, что зависло.
Вы уверены, что ребенок по-прежнему работает, и что это не просто процесс незавершенного зомби?
Ответ 5
Выполняет ли родительский процесс поток ошибок и выходных данных из дочернего процесса?
Если в некоторых ОС childprocess выдает некоторые ошибки/предупреждения на stdout/stderr, а родительский процесс не потребляет потоки, дочерний процесс блокирует и не достигает System.exit();
Ответ 6
У Hy я была та же проблема, но для меня было использование удаленной отладки (VmArgs: -Xdebug -Xrunjdwp: transport = dt_socket, address =% port%, server = y, suspend = y), когда я отключил этот процесс java.exe, как и ожидалось.
Ответ 7
Я думаю, что все очевидные причины были временно закрыты; например финализаторы, выключения крючков, не правильно дренируя стандартный вывод/стандартную ошибку в родительском процессе. Теперь вам нужно больше доказательств, чтобы понять, что происходит.
Предложения:
-
Настройте машину Windows XP или Vista (или виртуальную), установите соответствующую JRE и ваше приложение и попытайтесь воспроизвести проблему. Как только вы сможете воспроизвести проблему, либо присоедините отладчик, либо отправьте соответствующий сигнал, чтобы получить дамп потока при стандартной ошибке.
-
Если вы не можете воспроизвести проблему, как указано выше, попросите одного из ваших пользователей взять дамп потока и переслать файл журнала.
Ответ 8
Другой случай, не упомянутый здесь, - это то, что крючки выключения зависают на чем-то, поэтому будьте осторожны при написании кода hookdown (и если сторонняя библиотека зарегистрировала крюк выключения, который также висит).
Дин
Ответ 9
Проверьте, нет ли тупика.
Например,
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
System.exit(1);
и в close()
public void close() {
// some code
System.exit(1);
}
System.exit()
фактически вызывает обработчик завершения работы и финализаторы. Поэтому, если ваш хук завершения работы снова вызывает внутреннюю функцию exit() по какой-либо причине (например, когда close()
вызывает исключение, для которого вы хотите выйти из программы, вы также вызовете там exit()
). Вот так..
public void close() {
try {
// some code that raises exception which requires to exit the program
} catch(Exception exceptionWhichWhenOccurredShouldExitProgram) {
log.error(exceptionWhichWhenOccurredShouldExitProgram);
System.exit(1);
}
}
Хотя рекомендуется создавать исключение, некоторые могут выбрать вход и выход.
Также обратите внимание, что Ctrl+C
также не будет работать, если есть тупик. Так как он также вызывает хук отключения.
В любом случае, если это так, проблему можно решить с помощью этого обходного пути:
private static AtomicBoolean exitCalled=new AtomicBoolean();
private static void exit(int status) {
if(!exitCalled.get()) {
exitCalled.set(true);
System.exit(status);
}
}
Runtime.getRuntime().addShutdownHook(new Thread(MyClass::close));
exit(1);
}
private static void close() {
exit(1);
}
П.С.: Я чувствую, что вышеупомянутая версия exit()
должна быть написана только в методе System.exit()
(может быть, это какой-то PR для JDK?), потому что, практически нет смысла (по крайней мере из того, что я вижу) в развлекать тупик в System.exit()