Почему вызывать System.gc() - плохая практика?
После ответа на вопрос о том, как принудительно освобождать объекты в Java (парень очищал хэш-карту объемом 1,5 ГБ) с помощью System.gc()
, мне сказали, что вызывать вручную - плохая практикаSystem.gc()
, но комментарии были не совсем убедительными. Кроме того, никто, похоже, не осмелился ни повысить, ни понизить мой ответ.
Там мне сказали, что это плохая практика, но затем мне также сказали, что запуски сборщика мусора больше не останавливают мир систематически, и что это также может эффективно использоваться JVM только как подсказка, так что я в некотором роде в растерянности.
Я понимаю, что JVM обычно лучше вас знает, когда ей нужно освободить память. Я также понимаю, что беспокоиться о нескольких килобайтах данных глупо. Я также понимаю, что даже мегабайты данных - это уже не то, что было несколько лет назад. Но все же 1,5 гигабайта? И вы знаете, что в памяти хранится около 1,5 ГБ данных; это не похоже на выстрел в темноте. Это System.gc()
систематически плохо, или есть какой-то момент, когда это становится нормальным?
Итак, вопрос на самом деле двойной:
- Почему вызывать System . gc ( ) - плохая практика
System.gc()
? Действительно ли это просто подсказка для JVM в определенных реализациях или это всегда полный цикл сбора данных? Действительно ли существуют реализации сборщика мусора, которые могут выполнять свою работу, не останавливая мир? Пожалуйста, пролейте немного света на различные утверждения, сделанные людьми в комментариях к моему ответу. - Где пороговое значение? Никогда не стоит вызывать
System.gc()
, или бывают случаи, когда это приемлемо? Если да, то в каких случаях?
Переведено автоматически
Ответ 1
Причина, по которой все всегда советуют избегать, System.gc()
заключается в том, что это довольно хороший индикатор фундаментально неработающего кода. Любой код, корректность которого зависит от нее, безусловно, нарушен; любой код, который зависит от нее в плане производительности, скорее всего, нарушен.
Вы не знаете, с каким сборщиком мусора вы работаете. Конечно, есть такие, которые не "останавливают мир", как вы утверждаете, но некоторые JVM не настолько умны или по разным причинам (возможно, они на телефоне?) Этого не делают. Вы не знаете, что это будет делать.
Кроме того, это не гарантирует, что что-либо сделает. JVM может просто полностью проигнорировать ваш запрос.
Сочетание "вы не знаете, что это сделает", "вы даже не знаете, поможет ли это" и "вам все равно не нужно это вызывать" - вот почему люди так настойчиво говорят, что обычно вам не следует это вызывать. Я думаю, это тот случай, когда "если вам нужно спросить, следует ли вам использовать это, вы не должны"
ОТРЕДАКТИРУЙТЕ, чтобы решить несколько проблем из другого потока:
После прочтения темы, на которую вы ссылались, я хотел бы отметить еще несколько вещей. Сначала кто-то предположил, что вызов gc()
может вернуть системе память. Это, конечно, не обязательно верно - сама куча Java растет независимо от распределения Java.
Как и в случае, JVM будет содержать память (многие десятки мегабайт) и увеличивать кучу по мере необходимости. Это не обязательно возвращает эту память системе, даже когда вы освобождаете объекты Java; совершенно бесплатно сохранять выделенную память для использования при будущих выделениях Java.
Чтобы показать, что это возможно, System.gc()
ничего не делает, просмотрите ошибку JDK 6668279 и, в частности, что есть -XX:DisableExplicitGC
опция виртуальной машины:
По умолчанию вызовы
System.gc()
включены (-XX:-DisableExplicitGC
). Используйте-XX:+DisableExplicitGC
, чтобы отключить вызовыSystem.gc()
. Обратите внимание, что JVM по-прежнему выполняет сборку мусора, когда это необходимо.
Ответ 2
Уже объяснялось, что вызов system.gc()
может ничего не сделать, и что любой код, который "нуждается" в сборщике мусора для запуска, сломан.
Однако прагматическая причина, по которой вызывать это плохая практика System.gc()
, заключается в том, что это неэффективно. И в худшем случае это ужасно неэффективно! Позвольте мне объяснить.
Типичный алгоритм GC идентифицирует мусор, обходя все объекты, не являющиеся мусором, в куче и делая вывод, что любой не посещенный объект должен быть мусором. Исходя из этого, мы можем смоделировать общую работу по сборке мусора, состоящую из одной части, пропорциональной объему текущих данных, и другой части, пропорциональной количеству мусора; т.е. work = (live * W1 + garbage * W2)
.
Теперь предположим, что вы делаете следующее в однопоточном приложении.
System.gc(); System.gc();
Первый вызов (мы прогнозируем) выполнит (live * W1 + garbage * W2)
работу и избавит от накопившегося мусора.
Второй вызов выполнит (live* W1 + 0 * W2)
работу и ничего не вернет. Другими словами, мы выполнили (live * W1)
работу и абсолютно ничего не добились.
Мы можем смоделировать эффективность сборщика как объем работы, необходимый для сбора единицы мусора; т.е. efficiency = (live * W1 + garbage * W2) / garbage
. Итак, чтобы сделать GC максимально эффективным, нам нужно максимизировать значение garbage
при запуске GC; т. е. Дождаться, пока куча не заполнится. (А также сделайте кучу как можно больше. Но это отдельная тема.)
Если приложение не вмешивается (вызывая System.gc()
), перед запуском GC будет ждать, пока куча не заполнится, что приведет к эффективной сборке мусора1. Но если приложение принудительно запускает GC, есть вероятность, что куча не будет заполнена, и результатом будет неэффективный сбор мусора. И чем чаще приложение запускает GC, тем более неэффективным становится GC.
Примечание: приведенное выше объяснение умалчивает тот факт, что типичный современный GC разбивает кучу на "пробелы", GC может динамически расширять кучу, рабочий набор объектов, не содержащих мусора, в приложении может отличаться и так далее. Несмотря на это, один и тот же базовый принцип применяется повсеместно ко всем настоящим сборщикам мусора2. Принудительно запускать GC неэффективно.
1 - Вот как работает сборщик "пропускной способности". Параллельные сборщики, такие как CMS и G1, используют разные критерии, чтобы решить, когда запускать сборщик мусора.
2 - Я также исключаю менеджеры памяти, которые используют исключительно подсчет ссылок, но ни одна текущая реализация Java не использует этот подход ... по уважительной причине.
Ответ 3
Кажется, многие люди говорят вам не делать этого. Я не согласен. Если после большого процесса загрузки, такого как загрузка уровня, вы верите, что:
- У вас есть много объектов, которые недоступны и, возможно, не были собраны. и
- Вы думаете, пользователь мог бы смириться с небольшим замедлением на этом этапе
в вызове System.gc() нет ничего плохого. Я смотрю на это как на ключевое слово c / c ++ inline
. Это просто подсказка для gc, что вы, разработчик, решили, что время / производительность не так важны, как обычно, и что часть их можно было бы использовать для освобождения памяти.
Совет не полагаться на то, что он что-либо делает, правильный. Не полагайтесь на то, что он работает, но дать намек на то, что сейчас подходящее время для сбора данных, совершенно нормально. Я бы предпочел тратить время на тот момент в коде, когда это не имеет значения (экран загрузки), чем когда пользователь активно взаимодействует с программой (например, на уровне игры).)
Есть один момент, когда я буду принудительно собирать данные: при попытке выяснить, происходит ли утечка определенного объекта (либо машинного кода, либо большого, сложного взаимодействия с обратным вызовом. О, и любой компонент пользовательского интерфейса, который хотя бы мельком смотрит на Matlab.) Это никогда не должно использоваться в производственном коде.
Ответ 4
Люди проделали хорошую работу, объясняя, почему НЕ следует использовать, поэтому я расскажу вам пару ситуаций, в которых вам следует это использовать:
(Следующие комментарии применимы к Hotspot, работающему в Linux с CMS collector, где я с уверенностью говорю, что System.gc()
на самом деле всегда вызывает полную сборку мусора).
После первоначальной работы по запуску вашего приложения вы можете столкнуться с ужасным использованием памяти. Половина вашего штатного поколения может быть заполнена мусором, а это означает, что вы намного ближе к своей первой CMS. В приложениях, где это важно, неплохо вызвать System.gc(), чтобы "сбросить" вашу кучу до начального состояния текущих данных.
Аналогично # 1, если вы внимательно следите за использованием кучи, вы хотите иметь точное представление о том, каково ваше базовое использование памяти. Если первые 2 минуты безотказной работы вашего приложения полностью посвящены инициализации, ваши данные будут испорчены, если вы не принудительно (кхм ... "предложите") выполните полную сборку данных заранее.
Возможно, у вас есть приложение, которое разработано так, чтобы никогда ничего не продвигать постоянному поколению во время его работы. Но, возможно, вам нужно заранее инициализировать некоторые данные, которые не настолько велики, чтобы автоматически переходить к постоянному поколению. Если вы не вызовете System.gc() после того, как все настроите, ваши данные могут оставаться в новом поколении до тех пор, пока не придет время для их продвижения. Внезапно ваше приложение с супер-пупер низкой задержкой и низким уровнем GC сталкивается с ОГРОМНЫМ (условно говоря, конечно) штрафом за задержку при продвижении этих объектов во время обычных операций.
Иногда полезно иметь вызов System.gc, доступный в производственном приложении, для проверки наличия утечки памяти. Если вы знаете, что набор текущих данных в момент времени X должен существовать в определенном соотношении к набору текущих данных в момент времени Y, то может быть полезно вызвать System.gc() во время X и во время Y и сравнить использование памяти.