Разыскивание утечки памяти / проблема сборки "мусора" в Java

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

  1. Se-радио
  2. Hanselminutes
  3. Скалы.NET
  4. Полиморфный Подкаст
  5. RunAsRadio

у меня есть 35-минутное, добираются до работы, каждое утро (соединяют шиной), и мне нравится смотреть , Канал 9 питается моим Zune.

79
задан Jeremy S. 3 November 2015 в 16:01
поделиться

6 ответов

Итак, я наконец нашел проблему, был причиной этого, и я отправляю подробный ответ на случай, если у кого-то есть эти проблемы.

Я пробовал jmap, пока процесс не работал, но обычно это приводило к зависанию jvm и мне приходилось его запускать с --force. Это приводило к дампам кучи, в которых, казалось, не хватало большого количества данных или, по крайней мере, отсутствовали ссылки между ними. Для анализа я попробовал jhat, который представляет много данных, но не так много о том, как их интерпретировать. Во-вторых, я попробовал инструмент анализа памяти на основе eclipse ( http://www.eclipse.org/mat/ ), который показал, что куча - это в основном классы, связанные с tomcat.

Проблема заключалась в том, что jmap не сообщал о фактическом состоянии приложения, а только перехватывал классы при завершении работы, которые в основном были классами tomcat.

Я пробовал еще несколько раз, и заметил, что было очень много объектов модели (фактически в 2-3 раза больше, чем было помечено как общедоступные в базе данных).

Используя это, я проанализировал журналы медленных запросов и несколько не связанных с производительностью проблем. Я попробовал очень ленивую загрузку ( http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html ), а также заменил несколько операций гибернации прямым jdbc-запросы (в основном там, где речь идет о загрузке и работе с большими коллекциями - замены jdbc просто работали непосредственно с таблицами соединения), и заменил некоторые другие неэффективные запросы, которые регистрировал mysql.

Эти шаги улучшили производительность внешнего интерфейса, но все еще не решили проблему утечки, приложение все еще было нестабильным и действовало непредсказуемо.

Наконец, я обнаружил параметр: -XX: + HeapDumpOnOutOfMemoryError. В итоге получился очень большой (~ 6,5 ГБ) файл hprof, который точно отображал состояние приложения. По иронии судьбы, файл был настолько большим, что jhat не мог его проанализировать, даже на коробке с 16 ГБ оперативной памяти. К счастью, MAT смогла создать несколько красивых графиков и показать некоторые лучшие данные.

На этот раз выделялся единственный кварцевый поток, занимавший 4,5 ГБ из 6 ГБ кучи, и большую часть из них составлял StatefulPersistenceContext в спящем режиме ( https: //www.hibernate. org / hib_docs / v3 / api / org / hibernate / engine / StatefulPersistenceContext.html ). Этот класс используется спящим режимом внутри в качестве основного кеша (я отключил кеши второго уровня и запросы, поддерживаемые EHCache).

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

Ну, это была комбинация вещей: но spring не поддерживает сеанс без сохранения состояния), и я был бы очень удивлен, если бы в зрелом продукте произошла такая серьезная утечка памяти. Так почему же она протекала сейчас?

Ну, это была комбинация вещей: но spring не поддерживает сеанс без сохранения состояния), и я был бы очень удивлен, если бы в зрелом продукте произошла такая серьезная утечка памяти. Так почему же она протекала сейчас?

Ну, это была комбинация вещей: Пул кварцевых потоков создает экземпляры с некоторыми вещами threadLocal, spring внедрял фабрику сеансов, которая создавала сеанс в начале жизненного цикла кварцевых потоков, который затем повторно использовался для запуска различных кварцевых заданий, которые использовали сеанс гибернации. Затем Hibernate кэшировал в сеансе, что является его ожидаемым поведением.

Проблема в том, что пул потоков никогда не освобождает сеанс, поэтому спящий режим оставался резидентным и поддерживал кеш для всего жизненного цикла сеанса. Поскольку здесь использовалась поддержка шаблонов спящего режима пружин, явного использования сеансов не было (мы используем иерархию dao -> manager -> driver -> quartz-job, dao вводится с конфигурациями гибернации через spring, поэтому операции делается прямо по шаблонам).

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

Решение: создать метод dao, который явно вызывает session.flush () и session.clear () и вызывать этот метод в начале каждого задания.

Приложение работает уже несколько дней без проблем с мониторингом, ошибок памяти или перезапусков.

Спасибо всем за помощь, это был довольно сложная ошибка для отслеживания, поскольку все делало именно то, что должно было,

90
ответ дан 24 November 2019 в 10:18
поделиться

Можете ли вы запустить производственную коробку с включенным JMX?

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=<port>
...

Мониторинг и управление с помощью JMX

А затем подключиться с помощью JConsole, VisualVM ?

Это хорошо, чтобы сделать дамп кучи с помощью jmap ?

Если да, то вы можете проанализировать дамп кучи на предмет утечек с помощью JProfiler (у вас уже есть), jhat , VisualVM, Eclipse MAT . Также сравните дампы кучи, которые могут помочь найти утечки / шаблоны.

И, как вы упомянули, jakarta-commons. При использовании jakarta-commons-logging возникает проблема, связанная с удержанием загрузчика классов. Для хорошего прочтения этой проверки

День из жизни охотника за утечками памяти ( выпуск (Classloader) )

4
ответ дан 24 November 2019 в 10:18
поделиться

Я бы поискал напрямую выделенный ByteBuffer.

Из документации javadoc.

Прямой байтовый буфер может быть создан путем вызова фабричного метода allocateDirect этого класса. Буферы, возвращаемые этим методом, обычно имеют несколько более высокую стоимость выделения и освобождения, чем непрямые буферы. Содержимое прямых буферов может находиться за пределами обычной кучи со сборкой мусора, поэтому их влияние на объем памяти приложения может быть неочевидным. Поэтому рекомендуется выделять прямые буферы в первую очередь для больших долгоживущих буферов, на которые действуют собственные операции ввода-вывода базовой системы. В общем, лучше всего выделять прямые буферы только тогда, когда они дают измеримый выигрыш в производительности программы.

Возможно, код Tomcat использует это do для ввода-вывода; настроить Tomcat на использование другого коннектора.

В противном случае у вас может быть поток, который периодически выполняет System.gc (). "-XX: + ExplicitGCInvokesConcurrent" может быть интересным вариантом.

2
ответ дан 24 November 2019 в 10:18
поделиться

Есть ли JAXB? Я считаю, что JAXB - это средство для перманентного пространства.

Кроме того, я обнаружил, что visualgc , теперь поставляемый с JDK 6, является отличным способом увидеть, что происходит в памяти. Он прекрасно показывает райское пространство, пространство поколений и перманентное поведение, а также временное поведение GC. Все, что вам нужно, это PID процесса. Может быть, это поможет, пока вы работаете над JProfile.

А как насчет аспектов трассировки / ведения журнала Spring? Может быть, вы можете написать простой аспект, применить его декларативно и таким образом сделать профилировщик для бедняков.

1
ответ дан 24 November 2019 в 10:18
поделиться

«К сожалению, проблема также возникает время от времени, она кажется непредсказуемой, может работать в течение нескольких дней или даже недели без каких-либо проблем, или она может выходить из строя 40 раз в день, и единственное, что я могу постоянно улавливать, - это то, что сборщик мусора не работает ».

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

Если это происходит с помощью импорта XML, вам следует сравнить XML-данные за 40 сбоев в день с данными, который импортируется в день нулевого сбоя. Может быть, это какая-то логическая проблема, которую вы не найдете только в своем коде.

1
ответ дан 24 November 2019 в 10:18
поделиться

Похоже, утечка памяти, отличной от кучи, вы упомянули, что куча остается стабильной. Классическим кандидатом является permgen (постоянная генерация), которая состоит из двух вещей: загруженных объектов класса и интернированных строк. Поскольку вы сообщаете о подключении к VisualVM, вы должны иметь возможность увидеть количество загруженных классов, если будет продолжаться рост загруженных классов (важно, visualvm также показывает общее количество когда-либо загруженных классов, это хорошо, если это возрастет, но количество загруженных классов должно стабилизироваться через определенное время).

Если это действительно утечка permgen, то отладка становится более сложной, поскольку инструменты для анализа permgen скорее отсутствуют по сравнению с кучей. Лучше всего запустить небольшой скрипт на сервере, который неоднократно (каждый час?) Вызывает:

jmap -permstat <pid> > somefile<timestamp>.txt

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

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

Лучше всего запустить небольшой скрипт на сервере, который неоднократно (каждый час?) Вызывает:

jmap -permstat <pid> > somefile<timestamp>.txt

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

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

Лучше всего запустить небольшой скрипт на сервере, который неоднократно (каждый час?) Вызывает:

jmap -permstat <pid> > somefile<timestamp>.txt

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

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

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

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

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

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

4
ответ дан 24 November 2019 в 10:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: