Возможная утечка памяти в количестве загруженных классов в приложении Java - PullRequest
9 голосов
/ 03 октября 2008

Я недавно начал профилировать Java-приложение osgi, которое я пишу с использованием VisualVM. Я заметил одну вещь: когда приложение начинает отправлять данные клиенту (через JMS), количество загружаемых классов начинает расти с постоянной скоростью. Однако размер кучи и размер PermGen остается постоянным. Количество классов никогда не падает, даже после того, как оно перестает отправлять данные. Это утечка памяти? Я думаю, это так, потому что загруженные классы должны где-то храниться, однако куча и permgen никогда не увеличиваются даже после того, как я запускаю приложение в течение нескольких часов.

Для скриншота моего приложения для профилирования перейдите здесь

Ответы [ 6 ]

5 голосов
/ 03 октября 2008

Вы как-то динамически создаете новые классы на лету?

Спасибо за вашу помощь. Я понял, в чем проблема. В одном из моих классов я использовал Jaxb для создания строки XML. При этом JAXB использует отражение для создания нового класса.

JAXBContext context = JAXBContext.newInstance(this.getClass());

Итак, хотя JAXBContext не говорил в куче, классы были загружены.

Я снова запустил программу и вижу нормальное плато, как и ожидал.

4 голосов
/ 03 октября 2008

Готов поспорить, что ваша проблема связана с генерацией байт-кода.

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

Поскольку загрузчик классов содержится в каждом классе, это также означает, что вы не должны выпускать ссылки и на все классы [1]. Вы можете поймать их с приличным профилировщиком (я использую Yourkit - поиск нескольких экземпляров загрузчика классов с одинаковым сохраняемым размером)

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

-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled

(протестировано с JDK 1.5)

Даже в этом случае избыточная генерация байт-кода не является хорошей идеей, поэтому я предлагаю вам заглянуть в свой код, чтобы найти виновника и кэшировать сгенерированные классы. Частыми нарушителями являются языки сценариев, динамические прокси (в том числе созданные на серверах приложений) или огромная модель Hibernate (в этом случае вы можете просто увеличить свой permgen).

Смотри также:

  1. http://blogs.oracle.com/watt/resource/jvm-options-list.html
  2. http://blogs.oracle.com/jonthecollector/entry/presenting_the_permanent_generation
  3. http://forums.sun.com/thread.jspa?messageID=2833028
4 голосов
/ 03 октября 2008

Вы можете найти некоторые флаги горячих точек, которые будут полезны для понимания этого поведения, например:

  • -XX: + TraceClassLoading
  • -XX: + TraceClassUnloading

Это хорошая ссылка:

http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

3 голосов
/ 03 октября 2008

Если я не понимаю, мы смотрим здесь на загруженные классы, а не на экземпляры.

Когда ваш код впервые ссылается на класс, JVM заставляет ClassLoader выйти и извлечь информацию о классе из файла .class или подобного.

Я не уверен, при каких условиях это выгрузит класс. Конечно, он никогда не должен выгружать какой-либо класс со статической информацией.

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

Однако мне кажется странным две вещи:

  1. Почему это так линейно?
  2. Почему не плато?

Я бы ожидал, что он будет иметь тенденцию к росту, но в виде шаткой линии, а затем уменьшится по мере увеличения, поскольку JVM уже загрузила большинство классов, на которые ссылается ваша программа. Я имею в виду, что в большинстве приложений существует ограниченное число классов.

Вы как-то динамически создаете новые классы на лету?

Я бы предложил запустить более простое тестовое приложение через тот же отладчик, чтобы получить базовый вариант. Тогда вы могли бы рассмотреть возможность реализации своего собственного ClassLoader, который выплевывает некоторую отладочную информацию, или, возможно, есть инструмент, чтобы сделать его отчетным.

Вам нужно выяснить, что это за загружаемые классы.

0 голосов
/ 10 октября 2008

Используйте Eclipse Memory Analyzer для проверки дублированных классов и утечек памяти. Может случиться так, что один и тот же класс загружается более одного раза.

С уважением, Маркус

0 голосов
/ 03 октября 2008

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

В старом коде отношение слушателя заставляет объект слушателя оставаться вокруг. Я бы посмотрел на старые инструменты или те, которые не прошли через много оборотов. Любая давно существующая библиотека, работающая на более позднем JDK, будет знать о ссылочных объектах, что снимает требование «Удалить слушателя».

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

Не беспокойтесь о слушателях Swing или JDK, все они должны использовать ссылки, так что с вами должно быть все в порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...