Почему Java так долго ждать, чтобы запустить сборщик мусора? - программирование
Подтвердить что ты не робот

Почему Java так долго ждать, чтобы запустить сборщик мусора?

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

Heap Memory

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

Мои вопросы:

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

Я приношу извинения за свалку мозгов вопросов; Я отлаживаю эту проблему некоторое время и очень ценю любые указатели.

UPDATE
Я открыл дискуссию на PlayApps.net, указав на этот вопрос и упомянув некоторые из пунктов здесь; особенно комментарий @Affe относительно настроек для полного GC, которые устанавливаются очень консервативно, и @G_H комментируют настройки для начального и максимального размера кучи.

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

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

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

Еще раз спасибо за помощь, как всегда!

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

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

Три типа алгоритмов сбора данных

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

Коллекция копий/сборщиков

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

Коллекция Mark-compact

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

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

Инкрементный (поезд) сборник

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

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

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

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

Ответ 5

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

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

Ответ 6

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