Java: как восстановить зависшую нить?
Обратите внимание: я помечаю это с помощью JClouds, потому что, если вы прочитаете весь вопрос и комментарии к нему, я считаю, что это либо ошибка в JClouds, либо неправильное использование этой библиотеки.
У меня есть исполняемый JAR-файл, который работает, работает некоторое время, завершает работу без каких-либо ошибок / исключений, а затем зависает навсегда, когда он должен завершиться. Я профилировал его с помощью VisualVM (обращая внимание на запущенные потоки), а также бросил в оператор журнала для печати в точке (в конце main()
метод), где приложение висит. Вот последняя часть моего основного метода:
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for(Thread t : threadSet) {
String daemon = (t.isDaemon()? "Yes" : "No");
System.out.println("The ${t.getName()} thread is currently running; is it a daemon? ${daemon}.");
}
Когда мой JAR выполняет этот код, я вижу следующий вывод:
The com.google.inject.internal.util.Finalizer thread is currently running; is it a daemon? Yes.
The Signal Dispatcher thread is currently running; is it a daemon? Yes.
The RMI Scheduler(0) thread is currently running; is it a daemon? Yes.
The Attach Listener thread is currently running; is it a daemon? Yes.
The user thread 3 thread is currently running; is it a daemon? No.
The Finalizer thread is currently running; is it a daemon? Yes.
The RMI TCP Accept-0 thread is currently running; is it a daemon? Yes.
The main thread is currently running; is it a daemon? No.
The RMI TCP Connection(1)-10.10.99.8 thread is currently running; is it a daemon? Yes.
The Reference Handler thread is currently running; is it a daemon? Yes.
The JMX server connection timeout 24 thread is currently running; is it a daemon? Yes.
Я не думаю, что мне нужно беспокоиться о демонах (поправьте меня, если я ошибаюсь), поэтому отфильтруйте их по не-демонам:
The user thread 3 thread is currently running; is it a daemon? No.
The main thread is currently running; is it a daemon? No.
Очевидно, что основной поток все еще работает, потому что что-то мешает ему выйти. Хммм, user thread 3
выглядит интересно. Что VisualVM говорит нам?
Это представление потока в тот момент, когда приложение зависало (то, что происходило, когда вывод на консоль выше печатал). Хммм user thread 3
выглядит еще более подозрительным!
Поэтому, прежде чем убить приложение, я сделал дамп потока. Вот трассировка стека для user thread 3
:
"user thread 3" prio=6 tid=0x000000000dfd4000 nid=0x2360 waiting on condition [0x00000000114ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000782cba410> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Locked ownable synchronizers:
- None
Раньше мне никогда не приходилось анализировать один из них, поэтому для меня это означает бред (но, возможно, не для тренированного взгляда!).
После закрытия приложения временная шкала VisualVM перестает тикать / увеличиваться каждую секунду, и я могу прокручивать ее по горизонтали назад на временной шкале, чтобы user thread 3
был создан и начал свою жизнь как нытье поток:
Однако я не могу понять, как сказать, где в коде user thread 3
создается Поэтому я спрашиваю:
- Как я могу сказать, что создает
user thread 3
и где (особенно если я подозреваю, что это сторонняя библиотека OSS, которая создает поток), она создается? - Как я могу сортировать, диагностировать и исправлять зависание этой темы?
Обновить:
Вот мой код, который запускается примерно в то же время user thread 3
Кажется, создается:
ExecutorService myExecutor = Executors.newCachedThreadPool();
for(Node node : nodes) {
BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
myExecutor.execute(bootAndKickTask);
}
myExecutor.shutdown();
if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
log.error(toExc);
throw toExc;
}
Также вот мой GitHub Gist, который содержит полный дамп потока.
3 ответа
Было бы хорошо, если бы вы могли вставить весь код, который вы используете. Apache jclouds использует несколько исполнителей для выполнения определенных задач, и вы должны закрыть их.
Убедитесь, что вы звоните close()
метод в контексте или API вы получаете из Jclouds ContextBuilder
,
Кажется, что происходит, но я не могу подтвердить без кода, что вы забыли вызвать shutdown ()/ shutdownNow () для ExecutorService. Вы покидаете, как представляется, объект ThreadPoolExecutor, доступный глобально, и все еще работаете при выходе из основного потока. Поскольку он по-прежнему доступен глобально, ExecutorService никогда не вызовет свой метод finalize и никогда не отключится. По умолчанию потоки, созданные для ExecutorService, создаются как не-демоны и будут продолжать работать долго после того, как это потребуется.
Вы должны либо предоставить нам код для просмотра, либо просмотреть свой код там, где вы используете ThreadPoolExecutor, и правильно закрыть его после того, как вы его закончили.
Согласно документам:
Пул, на который больше нет ссылок в программе И нет оставшихся потоков, будет автоматически отключен. Если вы хотите, чтобы неиспользуемые пулы были восстановлены, даже если пользователи забыли вызвать shutdown(), вы должны организовать, чтобы неиспользуемые потоки в конечном счете умирали, устанавливая соответствующее время поддержания активности, используя нижнюю границу нулевых потоков ядра и / или установка allowCoreThreadTimeOut(логическое значение).
это означает, что даже если ваша программа больше не имеет ссылки на ThreadPoolExecutor, она никогда не будет возвращена, пока в пуле остается хотя бы один поток. Вы можете проверить документы для способов обойти это.
Есть две ошибки:
- Вы не можете безопасно освободить выделенный ресурс (пул потоков)
- Вы ловите ошибки, с которыми не справляетесь.
Вот потенциальное исправление. (Я не уверен, стоит ли нам включать ожидание завершения шага в блоке finally)
ExecutorService myExecutor = Executors.newCachedThreadPool();
try {
for(Node node : nodes) {
BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
myExecutor.execute(bootAndKickTask);
}
} finally {
myExecutor.shutdown();
if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
log.error(toExc);
throw toExc;
}
}