Подтвердить что ты не робот

Сборщик мусора G1: Пермский ген заполняется неопределенно долго, пока не будет выполнен полный GC

У нас есть довольно большое приложение, работающее на сервере приложений JBoss 7. Раньше мы использовали ParallelGC, но это давало нам проблемы на некоторых серверах, где куча была большой (5 ГБ и более) и обычно почти заполнялась, мы часто получали очень большие GC-паузы.

В последнее время мы улучшили использование памяти приложения и в нескольких случаях добавили больше ОЗУ на некоторые из серверов, на которых работает приложение, но мы также начали переходить на G1 в надежде сделать эти паузы менее частыми и/или короче. Вещи, похоже, улучшились, но мы наблюдаем странное поведение, которого раньше не было (с ParallelGC): Пермский генерал, кажется, заполняется довольно быстро, и как только он достигает максимального значения, запускается Full GC, что обычно вызывает длительную паузу в потоках приложений (в некоторых случаях, более 1 минуты).

Мы используем 512 МБ максимального размера в течение нескольких месяцев, и во время нашего анализа размер perm обычно будет расти примерно на 390 МБ с помощью ParallelGC. Однако после того, как мы перешли на G1, началось поведение выше. Я пробовал увеличивать максимальный размер до 1 ГБ и даже 1,5 ГБ, но все же происходят полные GC (они менее редки).

В этой ссылке вы можете увидеть скриншоты используемого нами инструмента профилирования (YourKit Java Profiler). Обратите внимание, что при запуске Full GC у Eden и Old Gen есть много свободного места, но максимальный размер Perm. Размер Perm и количество загруженных классов значительно уменьшаются после Full GC, но они снова начинают расти, и цикл повторяется. Кэш кода хорош, никогда не поднимается выше 38 МБ (в этом случае он составляет 35 МБ).

Вот отрезок журнала GC:

2013-11-28T11:15: 57.774-0300: 64445.415: [Полный GC 2126M- > 670M (5120M), 23.6325510 secs]    [Eden: 4096.0K (234.0M) → 0.0B (256.0M) Выжившие: 22.0M- > 0.0B Куча: 2126.1M (5120.0M) → 670.6M (5120.0M)]  [Times: user = 10.16 sys = 0.59, real = 23.64 secs]

Вы можете увидеть полный журнал здесь (с момента запуска сервера, до нескольких минут после полного GC).

Вот информация о среде:

java version "1.7.0_45"

Java (TM) SE Runtime Environment (сборка 1.7.0_45-b18)

Java HotSpot (TM) 64-разрядная серверная VM (сборка 24.45-b08, смешанный режим)

Параметры запуска: -Xms5g -Xmx5g -Xss256k -XX:PermSize=1500M -XX:MaxPermSize=1500M -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -Xloggc:gc.log

Итак, вот мои вопросы:

  • Является ли это ожидаемым поведением с G1? Я нашел еще одно сообщение в Интернете о том, что кто-то спрашивает что-то очень похожее и говорит, что G1 должен выполнять инкрементные коллекции в Перми, но ответа не было...

  • Есть ли что-то, что я могу улучшить/исправить в наших параметрах запуска? Сервер имеет 8 ГБ оперативной памяти, но, похоже, нам не хватает аппаратного обеспечения, производительность приложения прекрасна до тех пор, пока не будет запущен полный GC, когда пользователи испытывают большие задержки и начинают жаловаться.

4b9b3361

Ответ 1

Причины роста Perm Gen

  • Множество классов, особенно JSP.
  • Множество статических переменных.
  • Существует утечка загрузчика классов.

Для тех, кто не знает, вот простой способ подумать о том, как PremGen заполняется. У молодого генерала не хватает времени, чтобы все закончилось, и поэтому они переместились в пространство старого поколения. Пермский Ген имеет классы для объектов в Молодом и Ветхом Генеале. Когда объекты в Молодом или Старом Генеале собраны, а класс больше не ссылается, он получает "выгружен" из Пермского Генерала. Если Молодые и Молодые Старый генерал не получает GC'd, а затем и Пермский генерал, и как только он заполняется, ему нужен Full Stop-the-world GC. Для получения дополнительной информации см. Представление постоянного поколения.


Переход на CMS

Я знаю, что вы используете G1, но если вы переключитесь на сборщик низкоуровневых паролей Concurrent Mark Sweep (CMS) -XX:+UseConcMarkSweepGC, попробуйте включить сбор классов и коллекцию постоянного поколения, добавив -XX:+CMSClassUnloadingEnabled.


Скрытый Gotcha

Если вы используете JBoss, у RMI/DGC установлен gcInterval на 1 мин. Подсистема RMI заставляет полную сборку мусора раз в минуту. Это, в свою очередь, стимулирует продвижение, а не позволяет собирать его в Молодом поколении.

Вы должны изменить это, по крайней мере, на 1 час, если не 24 часа, чтобы GC выполнил правильные коллекции.

-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000

Список всех опций JVM

Чтобы просмотреть все параметры, запустите это из строки cmd.

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version

Если вы хотите увидеть, что использует JBoss, вам нужно добавить следующее к вашему standalone.xml. Вы получите список всех параметров JVM и того, для чего он установлен. ПРИМЕЧАНИЕ. Это должно быть в JVM, на который вы хотите посмотреть, чтобы использовать его. Если вы запустите его внешним, вы не увидите, что происходит в JVM, на котором работает JBoss.

set "JAVA_OPTS= -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal %JAVA_OPTS%"

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

-XX:+PrintcommandLineFlags

Диагностика

Используйте jmap, чтобы определить, какие классы потребляют пространство постоянного поколения. На выходе будет отображаться

  • загрузчик классов
  • # классов
  • байт
  • родительский загрузчик
  • живой/мертвый
  • типа
  • Итоги

    jmap -permstat JBOSS_PID  >& permstat.out
    

Параметры JVM

Эти настройки работали для меня, но в зависимости от того, как ваша система настроена и что делает ваше приложение, определит, подходят ли они для вас.

  • -XX:SurvivorRatio=8 - Устанавливает отношение пространства выживших к 1: 8, в результате чего большие пространства для оставшихся в живых (чем меньше отношение, тем больше пространство). SurvivorRatio - это размер пространства Эдена по сравнению с одним оставшимся в живых. Большие оставшиеся в живых пространства позволяют коротким объектам более длительный период времени умирать в молодости.

  • -XX:TargetSurvivorRatio=90 - Позволяет заняться 90% оставшихся в живых, вместо 50% по умолчанию, что позволяет лучше использовать память памяти оставшегося в живых.

  • -XX:MaxTenuringThreshold=31 - Чтобы предотвратить преждевременное продвижение от молодого поколения к поколению. Позволяет короткоживущим объектам более длительный период времени умирать в молодом поколении (и, следовательно, избегать продвижения по службе). Следствием этого параметра является то, что незначительные GC-времена могут увеличиваться из-за дополнительных объектов для копирования. Это значение и размеры пространства для оставшихся в живых могут нуждаться в корректировке, чтобы сбалансировать накладные расходы на копирование между оставшимися в живых пространствами и объектами владения, которые будут жить в течение длительного времени. Настройки по умолчанию для CMS: SurvivorRatio = 1024 и MaxTenuringThreshold = 0, которые заставляют всех оставшихся в живых убирать. Это может оказать большое давление на единую параллельную нить, собирающую поколение. Примечание: при использовании с -XX: + UseBiasedLocking этот параметр должен быть равен 15.

  • -XX:NewSize=768m - позволяет определять начальные размеры молодого поколения

  • -XX:MaxNewSize=768m - разрешить спецификацию максимальных размеров молодого поколения

Ниже приведен более подробный список JVM options.

Ответ 2

Является ли это ожидаемым поведением с G1?

Я не считаю это удивительным. Основополагающим предположением является то, что материал, помещенный в пергень, почти никогда не становится мусором. Таким образом, вы ожидаете, что пермген GC будет "последним средством"; то есть что-то, что JVM будет делать только в том случае, если его принуждают к полному GC. (ОК, этот аргумент нигде не близок к доказательству... но он соответствует следующему.)

Я видел много доказательств того, что у других коллекционеров одинаковое поведение; например.

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

Я думаю, что нашел тот же пост. Но кто-то считает, что это должно быть возможно, не очень поучительно.

Есть ли что-то, что я могу улучшить/исправить в наших параметрах запуска?

Я сомневаюсь. Я понимаю, что это присуще стратегии GCG.

Я предлагаю вам либо отследить, либо исправить то, что использует столько перменов, в первую очередь... или переключиться на Java 8, в котором больше нет кучи permgen: см. Исключение PermGen в JDK 8

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

  • чрезмерное использование String.intern(),
  • код приложения, который выполняет много динамического генерации класса; например используя DynamicProxy,
  • огромная кодовая база... хотя это не вызовет перманентного оттока, как вы, кажется, наблюдаете.

Ответ 3

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

  • Вы можете включить ведение журнала загрузки классов (-verbose: class, -XX: + TraceClassLoading -XX: + TraceClassUnloading,...) и вывести вывод
  • В тестовой среде вы можете попробовать контролировать (над JMX), когда классы загружаются (java.lang: type = ClassLoading LoadedClassCount). Это может помочь вам узнать, какая часть вашего приложения несет ответственность.
  • Вы также можете попробовать перечислить все классы с помощью инструментов JVM (извините, но я по-прежнему в основном использую jrockit и там вы будете делать это с помощью jrcmd. Hope Oracle перенес эти полезные функции в Hotspot...)

В общем, узнайте, что генерирует так много классов, а затем подумайте, как уменьшить это/настроить gc.

Cheers, Димо

Ответ 4

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

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

Симптом

Это произойдет только при повторном развертывании вашего приложения без перезапуск сервера приложений. Серия JBoss 4.0.x пострадала от такой утечки загрузчика класса. В результате я не смог перераспределить наше приложение более двух раз, прежде чем JVM закончится Память и авария PermGen.

Решение

Чтобы определить такую ​​утечку, разверните приложение и затем выполните полный дамп кучи (обязательно запустите GC перед этим). Затем проверьте, вы можете найти любой из ваших объектов приложения в дампе. Если так, следуйте их ссылкам на их корень, и вы найдете причину ваша утечка загрузчика. В случае JBoss 4.0 единственным решением было для перезапуска для каждого повторного развертывания.

Это то, что я постараюсь первым, ЕСЛИ вы думаете, что перераспределение может быть связано. Это сообщение в блоге является более ранним, делая то же самое, но также обсуждая детали. Основываясь на публикации, может быть, что вы на самом деле не перераспределяете что-либо, но перджен просто заполняется сам по себе. В этом случае рассмотрение классов + все, что добавлено в пермг, может быть способом (как уже упоминалось в предыдущем ответе).

Если это не даст больше понимания, мой следующий шаг будет проверять plumbr tool. У них есть своего рода гарантия на обнаружение утечки для вас.

Ответ 5

Вы должны запустить server.bat с командой java с помощью -verbose: gc