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, она никогда не будет возвращена, пока в пуле остается хотя бы один поток. Вы можете проверить документы для способов обойти это.

Есть две ошибки:

  1. Вы не можете безопасно освободить выделенный ресурс (пул потоков)
  2. Вы ловите ошибки, с которыми не справляетесь.

Вот потенциальное исправление. (Я не уверен, стоит ли нам включать ожидание завершения шага в блоке 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;
    }
}
Другие вопросы по тегам