Содержание

среда, 30 марта 2011 г.

Full GC: когда он срабатывает и как с этим бороться

Если вы заметили, что иногда ваше приложение зависает и выполняет какую-то операцию с заметной задержкой, то вполне возможно это вызывается полной сборкой мусора. Данную гипотезу можно проверить запустив виртуальную java машину с параметром -verbose:gc и сделав grep по Full GC в stdout.

Допустим вы нашли, что в вашем приложение срабатывает Full GC. Теперь давайте рассмотрим из-за чего это может происходить и как это лечить.

1. Явный вызов System.gc()

Поищите в вашем приложении вызов System.gc(). Проверьте нету ли рядом комментария, зачем это было сделано. Я часто видел, что данную строчку вставляют в код из абсолютно непонятных побуждений. Если вы нашли коментарий по типу "Let's clear heap before next memory consuming operation", скорее всего вы можете его удалить. Единственное легитимное использование данной инструкции, которое я могу себе представить, это когда ваше приложение работает очень долго, и есть промежутки времени (ночь), когда им никто не пользуется (например биржа закрыта в финансовых приложениях). Таким образом в это время можно вызвать Full GC, чтобы во время критической фазы приложения он случайно не сработал. Так же для CMS алгоритма он выполнит дефрагментацию памяти, что вобщем тоже очень полезно. Если же это не ваш случай и код менять по каким-то причинам вы не можете, то у вас в арсенале еще есть параметр -XX:+DisableExplicitGC, который проинструктирует JVM не делать Full GC на вызов System.gc().

2. Использование RMI

Если в логах вы видите, что Full GC срабатывает ровно каждую минуту (в java 5 и более старых JVM) или ровно каждый час (в java 6), то скорее всего вы используете RMI, из-за которого срабатывает полная сборка, чтобы почистить распределенные объекты. Это можно полечить выставив параметры JVM -Dsun.rmi.dgc.server.gcInterval=0x7FFFFFFFFFFFFFFE -Dsun.rmi.dgc.client.gcInterval=0x7FFFFFFFFFFFFFFE. Кстати, в некоторых случаях на старых JVM данные параметры не помогают. В этом случае можно применить тяжелую артиллерию и выставить -XX:+DisableExplicitGC.

3. CMS concurrent mode failure

Если в логах вы нашли "(concurrent mode failure)", то значит в вашем приложение сборкой мусора занимается low pause GC collector (еще его называют Concurrent Mark Sweep или коротко CMS). Данная ошибка случается, когда CMS стартовал слишком поздно и не успел закончится, и в это время сработала минорная сборка, причем свободного места OldGen осталось меньше, чем необходимо, чтобы запромоутить все требуемые объекты. Так же важно помнить, что CMS не выполняет дефрагментацию памяти, т.е. при очередной минорной сборки, места в OldGen может быть и достаточно, просто оно разбито на очень маленькие кусочки, так что запромоученные объекты все равно не влезают. Или еще вариант, что вы создаете очень большой объект, который попросту не влазит в YoungGen и сразу идет в OldGen. Тут уже без полной сборки не обойтись. Первым делом проверьте, что у вас Xms выставлено с тем же значением, что и Xmx. Вторым подумайте об увеличении Xmx или Xmn. Если же Full GC продолжает происходить то, необходимо выставить UseCMSInitiatingOccupancyOnly. Тогда GC гарантирует, что мажорная сборка будет запущена, не исходя из каких-то внутренних метрик, а как только уровень заполненности памяти достигнет CMSInitiatingOccupancyFraction. По умолчанию это 68%. Если все выше не подходит, можете еще попробовать настроить GC так, чтобы мажорных сборок было по минимуму (например увеличить Xmx), тогда и проблем дефрагментации не будет. Если же ваше приложение работает 24x7, то возможно все же у вас есть какой-то интервал на выходных или, наоборот, в понедельник в три часа ночи, когда системой пользуются мало и в этот момент вызвать System.gc() тем самым дефрагментировать память для эффективной работы мажорных сборок.
Еще хочется упомянуть, что Full GC по описанной причине может происходить из-за бага 6369448 в старых JVM (1.5 до версии 10 и 1.4.2 до версии 13), когда созданный объект не может уместиться в YoungGen даже после минорной сборки. Это происходило вне зависимости, есть ли достаточно места в OldGen или нет.

4. Сборка Permanent Generation

Так же Full GC срабатывает при сборке PermGen. Если у вас это происходит, то выставите больше размер этой области с помощью параметра MaxPermSize.  

5. Ложная тревога

Есть (были) несколько багов в JVM когда в логе печатается Full GC, а на самом деле происходит минорная сборка. Например, такое раньше случалось при использовании -XX:+CMSScavengeBeforeRemark. Еще был баг 6432427, который случался при вызове сборки мусора, когда JVM находилась критической JNI области. В этом случае тоже печаталась в логах полная сборка, хотя на самом деле ее не было.