У меня проблема с приложением, работающим в Fedora Core 6 с JDK 1.5.0_08.
После некоторого времени безотказной работы (обычно несколько дней) потоки начинают застревать в собственных методах.
Потоки заблокированы примерно так:
"pool-2-thread-2571" prio=1 tid=0x08dd0b28 nid=0x319e waiting for monitor entry [0xb91fe000..0xb91ff7d4]
at java.lang.Class.getDeclaredConstructors0(Native Method)
или же
"pool-2-thread-2547" prio=1 tid=0x75641620 nid=0x1745 waiting for monitor entry [0xbc7fe000..0xbc7ff554]
at sun.misc.Unsafe.defineClass(Native Method)
Особенно меня озадачивает вот это:
"HealthMonitor-10" daemon prio=1 tid=0x0868d1c0 nid=0x2b72 waiting for monitor entry [0xbe5ff000..0xbe5ff4d4]
at java.lang.Thread.dumpThreads(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:1383)
Потоки остаются заблокированными до перезапуска виртуальной машины.
Может ли кто-нибудь дать мне представление о том, что здесь происходит, что может вызывать блокировку собственных методов? Диапазон адресов записи монитора в верхней части каждого застрявшего потока отличается. Как я могу понять, что держит этот монитор?
Будем очень признательны за любые предложения или советы!
Спасибо, Дэйвид




Возможно, вам стоит использовать другую версию jdk.
Для вашего "загадочного" есть запись об ошибке для 1.5.0_08. Сообщается об утечке памяти (не знаю, связано ли это с вашей проблемой):
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469701
Также вы можете получить исходный код и посмотреть, что происходит в строке 1383. С другой стороны, это может быть просто дамп стека после возникновения исходной ошибки.
Мое первоначальное подозрение могло заключаться в том, что вы столкнулись с какой-то тупиковой ситуацией, связанной с загрузчиком классов. Я полагаю, что загрузка этого класса должна быть синхронизирована на каком-то уровне, потому что информация о классе станет доступной для всей виртуальной машины, а не только для потока, в который она была изначально загружена.
Тот факт, что методы на вершине стека являются собственными методами, кажется чистым совпадением, поскольку часть механизма загрузки классов реализуется таким образом.
Я хотел бы дополнительно исследовать, что происходит с загрузкой классов. Возможно, какой-то поток использует загрузчик классов для загрузки класса из сетевого местоположения, которое является медленным / недоступным и, таким образом, блокируется на очень долгое время, не передавая монитор другим потокам, которые хотят загрузить класс. Можно попробовать изучить вывод при запуске JVM с -verbose: class.
Несколько месяцев назад у меня были похожие проблемы, и я обнаружил, что утилита jthread (?) Бесценна. Вы даете ему идентификатор процесса для вашего Java-приложения, и он будет выгружать весь стек для каждого потока в вашем процессе.
Из вывода jthread я мог видеть, что один поток пытался получить блокировку после входа в монитор, а другой поток пытался войти в монитор после получения блокировки. Рецепт выхода из тупика.
Мне также было интересно, не возникало ли в вашем приложении проблемы со сборкой мусора. Вы говорите, что он работает пару дней, прежде чем он остановится вот так. Как долго вы позволяете ему оставаться в застрявшем состоянии, чтобы посмотреть, не закончит ли сборщик мусора?
Я думаю, инструмент называется jstack; Я использовал его для информации о ветке выше. Кроме того, отправка SIGQUIT (ctrl- \) в JVM сбрасывает потоки в стандартный вывод. Моя проблема в том, что кажется, что тупик происходит в коде, который я не могу контролировать (собственный код). Хотя я не сомневаюсь, что держу замок как-то ...
Можете ли вы узнать, какой поток на самом деле синхронизируется на мониторе, на котором ожидает собственный метод? По крайней мере, дамп потока, который вы получаете от виртуальной машины, когда вы отправляете ей SIGQUIT (kill -3), должен отображать эту информацию, как в
"Thread-0" prio=5 tid=0x0100b060 nid=0x84c000 waiting for monitor entry [0xb0c8a000..0xb0c8ad90]
at Deadlock.run(Deadlock.java:8)
- waiting to lock <0x255e5b38> (a java.lang.Object)
...
"main" prio=5 tid=0x01001350 nid=0xb0801000 waiting on condition [0xb07ff000..0xb0800148]
at java.lang.Thread.sleep(Native Method)
at Deadlock.main(Deadlock.java:21)
- locked <0x255e5b38> (a java.lang.Object)
В дампах, которые вы опубликовали до сих пор, я не вижу ни одного потока, который действительно ожидает блокировки определенного монитора ...
Я думаю, вы правы, что это связано с загрузчиком классов. Хотя потоки всегда блокируют собственные методы, я не думаю, что это совпадение. Нет классов, которые могут долго загружаться (или быть недоступными). Путь к классам включает только локальные jar-файлы. На самом деле существует более одного загрузчика классов, каждый из которых может претендовать на какой-либо ресурс и в результате оказаться заблокированным. Поскольку использование Groovy было удалено из приложения (это вызвало утечку памяти в PermGen), мы не видели дополнительных случаев блокировки. Так что это могло быть причиной проблемы.
К сожалению, явно тупикового монитора нет. Ни одно из значений, идентифицирующих мониторы, не повторяется в дампе потока. Если бы это было, я бы заметил: я думаю, что JVM сообщает о предполагаемых тупиковых потоках, а также есть плагин visualvm [Thread Dump Analyzer] [1], который помогает находить проблемы в дампах потоков.
Я нашел этот поток после того, как столкнулся с той же проблемой - JDK 1.6.0_23, работающий в Linux с Tomcat 6.0.29. Однако не уверен, что эти биты актуальны - я заметил, что помимо того, что многие потоки «застревали» в собственном методе getDeclaredConstructors (), ЦП был на 100% для процесса java. Итак, все потоки запросов застревают здесь, ЦП на 100%, дампы потоков не показывают каких-либо тупиковых ситуаций (и никаких других потоков, выполняющих какую-либо значительную активность), для меня это пахло сборщиком мусора. Разумеется, проверил логи сервера и обнаружил множество ошибок OutOfMemory - место в куче исчерпано.
Не могу сказать, что это будет основной причиной застревания потоков здесь каждый раз, но, надеюсь, информация здесь поможет другим, по крайней мере, исключить это как возможную причину ...
Если я не смогу доказать, что проблема вызвана ошибкой в версии JDK, которая была исправлена позже, обновление JDK невозможно. Завтра я попробую поискать исходный код - надеюсь, это поможет мне разобраться в проблеме. Хотя я помню, что у меня был дамп "смешанного нативного / Java" потока, который показывал следы нативного стека JDK, и я не мог ничего там увидеть ... Я работаю в Linux, поэтому это не похоже на ту ошибку, которую вы ссылка на актуальна.