Почему Java так долго ждет запуска сборщика мусора? - PullRequest
49 голосов
/ 19 августа 2011

Я создаю веб-приложение на Java, используя Play!Framework .Я размещаю его на playapps.net .Некоторое время я ломал голову над предоставленными графиками потребления памяти.Вот пример:

Heap Memory

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

Мои вопросы:

  • Справедливо ли для меня предположить, что у моего приложения нет утечки памяти, так как кажется, что вся память корректно восстанавливается сборщиком мусора при его запуске?
  • (из названия) Почему Java ждет последней возможной секунды, чтобы запустить сборщик мусора?Я наблюдаю значительное снижение производительности по мере того, как потребление памяти возрастает до верхней четверти графика.
  • Если мои утверждения выше верны, то как я могу решить эту проблему?Другие посты, которые я читал на SO, кажутся противоположными вызовам System.gc(), варьирующимся от нейтрального («это всего лишь запрос на запуск GC, поэтому JVM может просто игнорировать вас») до откровенно противоположного («код, опирающийся на * 1024»).* принципиально сломан ").Или я не здесь, и мне следует искать дефекты в моем собственном коде, которые вызывают такое поведение и периодически снижают производительность?

ОБНОВЛЕНИЕ
Я открыл дискуссию на PlayApps.net, указав на этот вопрос и упомянув некоторые моменты здесь;в частности, комментарий @ Affe относительно настроек для полного GC, устанавливаемых очень консервативно, и комментарий @ G_H о настройках для начального и максимального размера кучи.

Вот ссылка на обсуждение , хотя вам, к сожалению, нужна учетная запись playapps для ее просмотра.

Я сообщу здесь отзыв, когда получу его;Большое спасибо всем за ваши ответы, я уже многому научился у них!

Разрешение
Поддержка Playapps, которая все еще велика, не имела много предложений дляменя, их единственной мыслью было то, что, если бы я широко использовал кеш, это могло бы поддерживать объекты живыми дольше, чем нужно, но это не так.Я все еще выучил тонну (уууууу!) И дал @Ryan Amos зеленый чек, когда я принял его предложение звонить System.gc() каждые полдня, что на данный момент работает нормально.

Ответы [ 6 ]

22 голосов
/ 19 августа 2011

Любой подробный ответ будет зависеть от того, какой сборщик мусора вы используете, но есть некоторые вещи, которые в основном одинаковы для всех (современных, sun / oracle) GC.

Каждый раз, когда вы видитеиспользование в графе снизится, это сборка мусора.Единственный способ освобождения кучи - сбор мусора.Дело в том, что существует два типа сборщиков мусора, второстепенные и полные.Куча делится на две основные "области".Молодой и постоянный.(В действительности существует гораздо больше подгрупп.) Все, что занимает место в Янге и все еще используется, когда младший сборщик мусора освобождает память, «продвигается» в штатную.Когда что-то превращает скачок в постоянное владение, оно бездействует до тех пор, пока в куче не останется свободного места и не потребуется полная сборка мусора.

Итак, одна из интерпретаций этого графа состоит в том, что ваше молодое поколение довольно мало (по умолчаниюна некоторых JVM это может быть довольно небольшой% общей кучи), и вы сохраняете объекты "живыми" в течение сравнительно очень долгого времени.(возможно, вы держите ссылки на них в веб-сеансе?) Таким образом, ваши объекты «выживают» в сборках мусора до тех пор, пока не будут перемещены в постоянное пространство, где они остаются на неопределенное время до тех пор, пока JVM не будет полностью исправна.

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

19 голосов
/ 19 августа 2011

Java не будет запускать очиститель мусора, пока в этом нет необходимости, потому что очиститель мусора несколько замедляет работу и не должен запускаться так часто. Я думаю, что вы будете в порядке, чтобы запланировать уборку чаще, например, каждые 3 часа. Если приложение никогда не потребляет полную память, не должно быть никаких причин запускать очиститель мусора, поэтому Java запускает его только при очень большом объеме памяти.

В общем, не беспокойтесь о том, что говорят другие: делайте то, что работает лучше всего. Если вы обнаружите улучшения производительности от запуска уборщика мусора на 66% памяти, сделайте это.

12 голосов
/ 19 августа 2011

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

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

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

Две вещи, которые могут быть установлены для JVM при запуске, - это максимально допустимый размер кучи и начальный размер кучи. Максимальный предел - это жесткое ограничение. После того, как вы его достигнете, дальнейшая сборка мусора не уменьшит использование памяти, и если вам потребуется выделить новое пространство для объектов или других данных, вы получите OutOfMemoryError. Тем не менее, внутри есть и мягкое ограничение: текущий размер кучи. JVM не сразу поглощает максимальный объем памяти. Вместо этого он начинается с вашего первоначального размера кучи, а затем увеличивает кучу, когда это необходимо. Подумайте об этом как о памяти вашего JVM, которая может динамически увеличиваться.

Если фактическое использование памяти вашим приложением начинает достигать текущего размера кучи, сборка мусора обычно инициируется. Это может уменьшить использование памяти, поэтому увеличение размера кучи не требуется. Но также возможно, что приложению в настоящее время нужна вся эта память, и она будет превышать размер кучи. В этом случае он увеличивается при условии, что он еще не достиг максимального установленного предела.

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

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

2 голосов
/ 31 января 2012

У меня было приложение, которое генерировало такой график и действовало, как вы описалиЯ использовал коллектор CMS (-XX: + UseConcMarkSweepGC).Вот что происходило в моем случае.

У меня не было достаточно памяти, сконфигурированной для приложения, поэтому со временем я столкнулся с проблемами фрагментации в куче.Это приводило к тому, что GC становились все более и более частыми, но на самом деле он не выдавал OOME и не выходил из CMS на последовательный коллектор (что он и должен делать в этом случае), потому что статистика, которую он хранит, учитывает только время паузы приложения (блоки GC).время выполнения приложения (GC работает с потоками приложения) для этих вычислений игнорируется.Я настроил некоторые параметры, в основном дал ему большую кучу загружать больше кучи (с очень большим новым пространством), установил -XX: CMSFullGCsBeforeCompaction = 1, и проблема перестала возникать.

2 голосов
/ 19 августа 2011

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

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

Три типа алгоритмов сбора

HotSpot JVM предоставляет три алгоритма GC, каждый из которых настроен на определенный тип коллекции в пределах определенного поколения. Коллекция copy (также известная как scavenge) быстро очищает недолговечные объекты в куче нового поколения. Алгоритм mark-compact использует более медленную и надежную технику для сбора долгоживущих объектов в куче старого поколения. Инкрементный алгоритм пытается улучшить коллекцию старого поколения, выполняя надежный сборщик мусора при минимальных паузах.

Копирование / очистка коллекции

Используя алгоритм копирования, JVM восстанавливает большинство объектов в объектном пространстве нового поколения (также известном как eden), просто делая небольшие очистки - Java-термин для сбора и удаления мусора. Долгоживущие объекты в конечном итоге копируются или хранятся в старом объектном пространстве.

Марк-компактная коллекция

По мере того, как большее количество объектов становится арендованным, пространство старых объектов начинает достигать максимальной загрузки. Алгоритм mark-compact, используемый для сбора объектов в старом объектном пространстве, имеет другие требования, чем алгоритм сбора копий, используемый в новом объектном пространстве.

Алгоритм mark-compact сначала сканирует все объекты, отмечая все достижимые объекты. Затем он уплотняет все оставшиеся пробелы мертвых объектов. Алгоритм mark-compact занимает больше времени, чем алгоритм сбора копий; однако он требует меньше памяти и устраняет фрагментацию памяти.

Инкрементная (поездная) коллекция

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

Инкрементная коллекция разбивает старые паузы коллекции объектов на множество крошечных пауз даже с большими областями объектов. Вместо нового и старого поколения этот алгоритм имеет среднее поколение, состоящее из множества небольших пространств. С инкрементным сбором связаны некоторые накладные расходы; Вы можете увидеть снижение скорости на 10%.

Параметры -Xincgc и -Xnoincgc управляют использованием инкрементного сбора. В следующем выпуске HotSpot JVM, версия 1.4, будет предпринята попытка непрерывного, без паузы GC, что, вероятно, будет разновидностью инкрементного алгоритма. Я не буду обсуждать инкрементный сбор, поскольку он скоро изменится.

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

0 голосов
/ 19 августа 2011

Возможно, у вас есть утечки памяти, которые очищаются каждые 24 часа.

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