Classloader удерживается слабыми ссылками?

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

Если исключить слабые ссылки, список пуст! Как это возможно, поскольку объект, содержащий только слабые ссылки, должен собирать мусор? (Я выполнял GC много раз в jconsole)

Если я включаю слабые ссылки, я получаю список ссылок, все из которых относятся к одному из следующих полей:

  • java.lang.reflect.Proxy.loaderToCache
  • java.lang.reflect.Proxy.proxyClasses
  • java.io.ObjectStreamClass $Caches.localDescs
  • java.io.ObjectStreamClass $Caches.reflectors
  • java.lang.ref.Finalizer.unfinalized

Я не мог найти никакой причины, почему любая из этих ссылок должна предотвращать сбор мусора загрузчика классов. Это ошибка gc? Специальный недокументированный случай? Ошибка jmap/jhat? Или что?

И самое странное... после того, как время от времени простаивало и время от времени в течение примерно 40 минут, ничего не меняя, он решил разгрузить классы и собрать загрузчик классов.

Примечание:

Если вы заявляете о задержке сбора загрузчиков классов или слабых ссылок, укажите, пожалуйста, обстоятельства, в которых это происходит, и в идеале:

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

Если вы считаете, что поведение зависит от реализации, то, пожалуйста, сосредоточьтесь на том, что происходит в oracle или icedtea jvm, версия 6 или 7 (выберите любой из них и будьте конкретными).

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

Ответ 1

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

От на этой странице: "объекты с мягкой досягаемостью останутся в живых в течение некоторого времени после последнего обращения к ним. Значение по умолчанию одна секунда продолжительности жизни на один свободный мегабайт в куче. Это значение можно настроить с помощью флага -XX: SoftRefLRUPolicyMSPerMB"

Итак, я настроил этот флаг на 1, и загрузчик классов был собран в течение нескольких секунд!

Я думаю, что мягкая ссылка исходит от ObjectStreamClass. Вопрос в том, почему jhat не показывает его в ссылках на корневые ссылки. Это потому, что оно не является ни сильным, ни слабым? Или потому, что он уже нашел слабые ссылки из тех же статических полей? Или какая-то другая причина? В любом случае, я думаю, что это нужно улучшить в jhat.

Ответ 2

Классы находятся в специальном пространстве памяти - постоянном поколении. Чтобы разгрузить загрузчик классов. GC должен выбрать, чтобы включить пространственное пространство в объем коллекции. Различные алгоритмы GC имеют немного другое поведение, но, как правило, GC будет пытаться избежать сбора пространственных данных.

По моему опыту, даже если classloader недоступен, JVM может закончиться OutOfMemoryError, прежде чем он попытается собрать пространство PERM.