После ответа на вопрос о том, как принудительно освобождать объекты в Java (парень очищал HashMap размером 1,5 ГБ) с помощью System.gc(), мне сказали, что вызывать System.gc() вручную — плохая практика, но комментарии не были вполне убедительно. Вдобавок, похоже, никто не осмелился проголосовать ни за, ни против моего ответа.
Мне там сказали, что это плохая практика, но потом мне также сказали, что запуски сборщика мусора больше не останавливают мир систематически, и что JVM также может эффективно использовать его только как подсказку, так что я вроде как в убыток.
Я понимаю, что JVM обычно лучше вас знает, когда ей нужно освободить память. Я также понимаю, что беспокоиться о нескольких килобайтах данных глупо. Я также понимаю, что даже мегабайты данных уже не те, что были несколько лет назад. Но все же, 1,5 гигабайта? И вы знаете, что в памяти висит около 1,5 ГБ данных; это не похоже на выстрел в темноте. Является ли System.gc() систематически плохой или есть какой-то момент, когда все становится хорошо?
Так что вопрос на самом деле двойной:
Почему вызов System.gc() является плохой практикой? Это действительно просто намек на JVM в определенных реализациях или это всегда полный цикл сбора? Существуют ли действительно реализации сборщиков мусора, которые могут выполнять свою работу, не останавливая мир? Пожалуйста, пролейте свет на различные утверждения, сделанные людьми в комментариях к моему ответу.
Где порог? Никогда не стоит вызывать System.gc() или бывают случаи, когда это допустимо? Если да, то какие это времена?
Причина, по которой все всегда говорят избегать System.gc(), заключается в том, что это довольно хороший индикатор фундаментально неработающего кода. Любой код, корректность которого зависит от него, безусловно, неисправен; любые, которые полагаются на него для производительности, скорее всего, сломаны.
Вы не знаете, под каким сборщиком мусора вы работаете. Конечно, есть некоторые, которые не «останавливают мир», как вы утверждаете, но некоторые JVM не такие умные или по разным причинам (возможно, они на телефоне?) этого не делают. Вы не знаете, что это будет делать.
Кроме того, ничего не гарантировано. JVM может просто полностью проигнорировать ваш запрос.
Комбинация «вы не знаете, что он сделает», «вы не знаете, поможет ли он вообще» и «вам все равно не нужно вызывать его» — вот почему люди так настойчиво говорят, что обычно Вы не должны называть это. Я думаю, что это случай «если вам нужно спросить, следует ли вам использовать это, вы не должны»
РЕДАКТИРОВАТЬ, чтобы решить несколько проблем из другого потока:
После прочтения темы, на которую вы ссылаетесь, есть еще несколько вещей, которые я хотел бы отметить. Во-первых, кто-то предположил, что вызов gc() может вернуть память системе. Это, конечно, не обязательно верно — сама куча Java растет независимо от выделений Java.
Например, JVM будет хранить память (многие десятки мегабайт) и увеличивать кучу по мере необходимости. Это не обязательно возвращает эту память системе, даже когда вы освобождаете объекты Java; можно совершенно свободно удерживать выделенную память для использования в будущих распределениях Java.
Чтобы показать, что System.gc() может ничего не делать, просмотрите ошибку JDK 6668279 и, в частности, наличие опции -XX:DisableExplicitGC VM:
По умолчанию вызовы System.gc() включены (-XX:-DisableExplicitGC). Используйте -XX:+DisableExplicitGC, чтобы отключить вызовы System.gc(). Обратите внимание, что JVM по-прежнему выполняет сборку мусора, когда это необходимо.
Уже было объяснено, что вызов system.gc() может ничего не дать, и что любой код, который «нуждается» в сборщике мусора для работы, сломан.
Однако прагматическая причина, по которой вызов System.gc() является плохой практикой, заключается в том, что это неэффективно. А в худшем случае — ужасно неэффективно! Позвольте мне объяснить.
Типичный алгоритм GC определяет мусор, обходя все не-мусорные объекты в куче и делая вывод, что любой объект, который не был посещен, должен быть мусором. Исходя из этого, мы можем смоделировать общую работу сборки мусора, состоящую из одной части, пропорциональной количеству живых данных, и другой части, пропорциональной количеству мусора; то есть work = (live * W1 + garbage * W2).
Теперь предположим, что вы делаете следующее в однопоточном приложении.
System.gc(); System.gc();
Первый вызов (по нашим прогнозам) выполнит работу (live * W1 + garbage * W2) и избавится от оставшегося мусора.
Второй вызов выполнит работу (live * W1 + 0 * W2) и ничего не вернет. Другими словами, мы проделали (live * W1) работу и не достигли абсолютно ничего.
Мы можем представить эффективность сборщика как количество работы, необходимое для сбора единицы мусора; т.е. эффективность = (live * W1 + garbage * W2) / garbage. Поэтому, чтобы сделать GC максимально эффективным, нам нужно максимизировать значение мусора, когда мы запускаем GC; т.е. ждать, пока куча не заполнится. (А также сделать кучу как можно больше. Но это отдельная тема).
Если приложение не вмешивается (вызывая System.gc()), GC будет ждать, пока куча не заполнится перед запуском, что приведет к эффективной сборке мусора1. Но если приложение заставляет GC запускаться, есть вероятность, что куча не будет заполнена, и в результате мусор будет собираться неэффективно. И чем чаще приложение заставляет GC, тем более неэффективным становится GC.
Примечание: приведенное выше объяснение не учитывает тот факт, что типичный современный GC разделяет кучу на «пространства», GC может динамически расширять кучу, рабочий набор не-мусорных объектов приложения может меняться и так далее. Тем не менее, один и тот же основной принцип применим ко всем настоящим сборщикам мусора2. Заставлять GC работать неэффективно.