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




Да, обычно это утечка памяти (поскольку мы не имеем дела с памятью напрямую, это скорее утечка экземпляра класса). Я проходил через этот процесс раньше, и обычно это какой-то слушатель, добавленный к старому набору инструментов, который не удалял его самостоятельно.
В более старом коде отношение слушателя заставляет объект «слушатель» оставаться. Я бы посмотрел на старые наборы инструментов или на те, которые не претерпели много изменений. Любая давно существующая библиотека, работающая на более позднем JDK, будет знать о ссылочных объектах, что устраняет требование «Удалить прослушиватель».
Кроме того, вызывайте dispose для своих окон, если вы каждый раз их воссоздаете. Я не думаю, что они когда-нибудь уйдут, если вы этого не сделаете (на самом деле есть также удаление при закрытии).
Не беспокойтесь о слушателях Swing или JDK, все они должны использовать ссылки, так что все будет в порядке.
Если я не неправильно понял, мы смотрим здесь на загруженные классы, а не на экземпляры.
Когда ваш код впервые ссылается на класс, JVM заставляет ClassLoader выйти и получить информацию о классе из файла .class или подобного.
Я не уверен, при каких условиях это выгружает класс. Конечно, он никогда не должен выгружать какой-либо класс со статической информацией.
Поэтому я ожидал, что шаблон будет примерно таким, как у вас, где по мере выполнения вашего приложения оно переходит в области и ссылается на новые классы, поэтому количество загруженных классов будет расти и расти.
Однако мне кажутся странными две вещи:
Я ожидал, что он будет иметь тенденцию к росту, но в виде шаткой линии, а затем сузится по мере увеличения, поскольку JVM уже загрузила большинство классов, на которые ссылается ваша программа. Я имею в виду, что в большинстве приложений есть ссылки на конечное количество классов.
Вы каким-то образом динамически создаете новые классы на лету?
Я бы предложил запустить более простое тестовое приложение через тот же отладчик, чтобы получить базовый вариант. Затем вы можете подумать о реализации своего собственного ClassLoader, который выводит некоторую отладочную информацию, или, может быть, есть инструмент для создания отчетов.
Вам нужно выяснить, что это за загружаемые классы.
Вы можете найти некоторые флаги точек доступа, которые могут быть полезны для понимания этого поведения, например:
Это хорошая ссылка:
http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
Are you dynamically creating new classes on the fly somehow?
Спасибо за вашу помощь. Разобрался в чем проблема. В одном из моих классов я использовал Jaxb для создания строки XML. При этом JAXB использует отражение для создания нового класса.
JAXBContext context = JAXBContext.newInstance(this.getClass());
Итак, хотя JAXBContext и не говорил в куче, классы были загружены.
Я снова запустил свою программу и, как и ожидал, вижу нормальное плато.
«Я снова запустил свою программу», какие изменения вы внесли, чтобы увидеть нормальное плато? Этот ответ бесполезен.
Готов поспорить, что ваша проблема связана с генерацией байт-кода.
Многие библиотеки используют CGLib, BCEL, Javasist или Janino для генерации байт-кода для новых классов во время выполнения, а затем загружают их из управляемого загрузчика классов. Единственный способ освободить эти классы - освободить все ссылки на загрузчик классов.
Поскольку загрузчик классов принадлежит каждому классу, это также означает, что вы не должны освобождать ссылки также и на все классы [1]. Вы можете поймать их с помощью приличного профилировщика (я использую Yourkit - ищите несколько экземпляров загрузчика классов с одинаковым сохраненным размером)
Одна загвоздка заключается в том, что JVM не выгружает классы по умолчанию (причина в обратной совместимости - люди предполагают (ошибочно), что статические инициализаторы будут выполняться только один раз. Правда в том, что они выполняются каждый раз при загрузке класса). включить разгрузку, вы должны передать некоторые используйте следующие параметры:
-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled
(проверено с JDK 1.5)
Даже в этом случае чрезмерная генерация байт-кода не является хорошей идеей, поэтому я предлагаю вам заглянуть в свой код, чтобы найти виновника и кэшировать сгенерированные классы. Частыми нарушителями являются языки сценариев, динамические прокси (включая те, которые генерируются серверами приложений) или огромная модель Hibernate (в этом случае вы можете просто увеличить свой permgen).
Смотрите также:
Даже JVM будет генерировать классы для java.lang.reflect.Proxy и подогревать Field.get / set, Method.invoke и Constructor.newInstance (IIRC).
Да, вы правы, все же эти сгенерированные классы кэшируются в слое отражения, поэтому они не должны быть причиной утечек permgen.
Используйте Анализатор памяти Eclipse для проверки дублированных классов и утечек памяти. Может случиться так, что один и тот же класс загружается более одного раза.
С уважением, Маркус
Могу я вас спросить, как вы избавились от создания нового класса? -Я борюсь с этой проблемой и хочу попробовать ваше решение. -Спасибо