Не удалось избежать продвижения в Java CMS GC
У меня есть Java-приложение, использующее сборку мусора CMS, которая страдает от полного GC ParNew (продвижение не удалось) несколько раз в день (см. Пример ниже). Я понимаю, что сбой продвижения происходит, когда сборщик мусора не может найти достаточно (непрерывного) пространства в старом поколении, в которое можно продвинуть объект из нового поколения. На этом этапе он вынужден делать дорогостоящий полный сборщик мусора "стоп-мир". Я хочу избежать таких событий.
Я прочитал несколько статей, которые предлагают возможные решения, но я хотел уточнить / консолидировать их здесь:
- -Xmx: увеличить размер кучи, например. от 2G до 4G - простое решение для увеличения запаса мощности в старом поколении - кажется, работает достаточно хорошо в моем опыте
- -XX:NewRatio: увеличить NewRatio, например. от 2 до 4, для того, чтобы увеличить старое поколение / уменьшить новое поколение - дать старому поколению больше места - похоже, пока что не имеет большого, если вообще есть, эффекта от моих экспериментов
- -XX:PromotedPadding: увеличить количество отступов, предусмотренных для предотвращения сбоев продвижения, однако я не могу найти никаких предложений о том, какие значения дать для этого параметра, кто-нибудь знает, что означает значение, что такое значение по умолчанию или какие значения пытаться?
- -XX: CMSInitiatingOccupancyFraction -XX: + UseCMSInitiatingOccupancyOnly: сделать запуск цикла CMS раньше, чтобы избежать недостатка места в старом поколении - я еще не пробовал это решение - какие значения было бы разумно попробовать? Что такое по умолчанию?
- Не выделяйте очень большие объекты в куче: очень большой объект может быть трудно продвинуть, поскольку он потребует большого непрерывного количества свободного пространства в старом поколении - это не относится к моему приложению, насколько я знаю
В случае, если это уместно, вот мои текущие параметры GC и пример журналов, предшествующих событию неудачного продвижения.
-Xmx4g -XX:+UseConcMarkSweepGC -XX:NewRatio=1
2014-12-19T09:38:34.304+0100: [GC (Allocation Failure) [ParNew: 1887488K->209664K(1887488K), 0.0685828 secs] 3115998K->1551788K(3984640K), 0.0690028 secs] [Times: user=0.50 sys=0.02, real=0.07 secs]
2014-12-19T09:38:35.962+0100: [GC (Allocation Failure) [ParNew: 1887488K->208840K(1887488K), 0.0827565 secs] 3229612K->1687030K(3984640K), 0.0831611 secs] [Times: user=0.39 sys=0.03, real=0.08 secs]
2014-12-19T09:38:39.975+0100: [GC (Allocation Failure) [ParNew: 1886664K->114108K(1887488K), 0.0442130 secs] 3364854K->1592298K(3984640K), 0.0446680 secs] [Times: user=0.31 sys=0.00, real=0.05 secs]
2014-12-19T09:38:44.818+0100: [GC (Allocation Failure) [ParNew: 1791932K->167245K(1887488K), 0.0588917 secs] 3270122K->1645435K(3984640K), 0.0593308 secs] [Times: user=0.57 sys=0.00, real=0.06 secs]
2014-12-19T09:38:49.239+0100: [GC (Allocation Failure) [ParNew (promotion failed): 1845069K->1819715K(1887488K), 0.4417916 secs][CMS: 1499941K->647982K(2097152K), 2.4203021 secs] 3323259K->647982K(3984640K), [Metaspace: 137778K->137778K(1177600K)], 2.8626552 secs] [Times: user=3.46 sys=0.01, real=2.86 secs]
2 ответа
Хотя увеличение памяти действительно является самым простым и наиболее общим решением, в этом случае кажется, что у нас была конкретная проблема, которая требовала конкретного решения. Глядя на логи GC в моем случае, я бы увидел такие логи:
GC (CMS Initial Mark) [1 CMS-initial-mark: 2905552K(3145728K)]
что показывает, что старый ген был заполнен на ~92% в начале CMS (использовалось 2,9 ГБ из 3,1 ГБ). Таким образом, JVM решила, что "доля занятости" должна составлять около 90%. Это изменение по умолчанию, которое начинается с того, что, я думаю, составляет около 68%.
Очевидно, мое приложение ведет себя так, что JVM считает, что это хорошо. Но тогда приложение, кажется, удивляет JVM, внезапно требуя больше места в старом поколении для продвижения объектов из нового поколения.
При добавлении флагов GC
-XX:CMSInitiatingOccupancyFraction=50 -XX:+UseCMSInitiatingOccupancyOnly
мы больше не видели каких-либо событий "провал промоушена". Эти флаги, соответственно, устанавливают начальную долю занятости на 50% и говорят JVM не изменять эту долю. Поэтому, как только старый ген превысит 50%, он запустит CMS. Это позволяет избежать ожидания до тех пор, пока заполняемость не достигнет 90% или около того, где вероятность "провала промоушена" намного выше.
Увеличение памяти - самый простой подход. Существует риск того, что память в конечном итоге будет фрагментирована (в крайних случаях). Я предлагаю вам сделать кучу как минимум в 2,5 раза больше памяти, используемой после полного GC.
Полный сборщик мусора в CMS настолько дорог, что он представляет собой последовательную коллекцию, а не параллельную коллекцию.
Альтернативой является использование параллельной коллекции, которая дефрагментирует и не возвращается к последовательной коллекции.
Сетевые буферы и длинные строки - это большие объекты. Если бы они были действительно большими, они бы попали прямо в пространство владения, они выглядят как более крупные объекты в новом пространстве, которые не могут быть скопированы в пространство пребывания.