Перейти к основному содержимому

Руководство по System.gc()

· 6 мин. чтения

1. Обзор

В этом руководстве мы собираемся исследовать метод System.gc() , расположенный в пакете java.lang .

Явный вызов System.gc() известен как плохая практика. Давайте попробуем понять, почему и есть ли какие-либо варианты использования, когда вызов этого метода может быть полезен.

2. Сбор мусора

Виртуальная машина Java решает выполнить сборку мусора, когда есть признаки для этого. Эти указания отличаются от одной реализации GC к другой. Они основаны на разных эвристиках. Тем не менее, есть несколько моментов, когда GC будет выполнен точно:

  • Старое поколение (постоянное пространство) заполнено, что запускает основной/полный GC
  • Новое поколение (ячейки Eden + Survivor0 + Survivor1) заполнено, что вызывает незначительный GC

Единственное, что не зависит от реализации сборщика мусора, — это право объекта на сборку мусора.

Теперь мы рассмотрим сам метод System.gc() .

3. Система.gc()

Вызов метода прост:

System.gc()

В официальной документации Oracle говорится, что:

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

Нет никакой гарантии, что настоящий сборщик мусора будет активирован .

System.gc() запускает основной сборщик мусора . Следовательно, есть риск потратить некоторое время на фазу остановки мира, в зависимости от вашей реализации сборщика мусора. В результате мы имеем ненадежный инструмент с потенциально значительным снижением производительности .

Существование явного вызова сборки мусора должно быть серьезным красным флагом для всех.

Мы можем запретить System.gc() выполнять какую-либо работу, используя флаг JVM -XX:DisableExplicitGC .

3.1. Настройка производительности

Стоит отметить, что непосредственно перед выдачей OutOfMemoryError JVM выполнит полный сборщик мусора. Поэтому явный вызов System.gc() не спасет нас от провала .

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

В случае проблем с памятью у нас есть набор настроек , которые мы можем изменить, чтобы настроить наше приложение — начиная с выбора другого сборщика мусора, через установку желаемого соотношения времени приложения/времени GC и, наконец, заканчивая установкой фиксированных размеров для сегментов памяти.

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

-XX:+ExplicitGCInvokesConcurrent

или же:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

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

В следующей главе мы увидим практический пример, когда явный вызов System.gc() кажется полезным.

4. Пример использования

4.1. Сценарий

Давайте напишем тестовое приложение. Мы хотим найти ситуацию, когда вызов System.gc() может быть полезен .

Мелкая сборка мусора происходит чаще, чем крупная. Таким образом, мы, вероятно, должны сосредоточиться на последнем. Один объект перемещается в постоянное пространство, если он «пережил» несколько коллекций и по-прежнему доступен из корней GC.

Представим, что у нас есть огромная коллекция объектов, живущих какое-то время. Затем, в какой-то момент, мы очищаем коллекцию объектов. Может быть, сейчас хороший момент для запуска System.gc() ?

4.2. Демонстрационное приложение

Мы создадим простое консольное приложение, которое позволит нам смоделировать этот сценарий:

public class DemoApplication {

private static final Map<String, String> cache = new HashMap<String, String>();

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

while (scanner.hasNext()) {
final String next = scanner.next();
if ("fill".equals(next)) {
for (int i = 0; i < 1000000; i++) {
cache.put(randomUUID().toString(), randomUUID().toString());
}
} else if ("invalidate".equals(next)) {
cache.clear();
} else if ("gc".equals(next)) {
System.gc();
} else if ("exit".equals(next)) {
System.exit(0);
} else {
System.out.println("unknown");
}
}
}
}

4.3. Запуск демонстрации

Давайте запустим наше приложение с несколькими дополнительными флагами:

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

Первые два флага необходимы для регистрации информации GC. Следующие два флага устанавливают начальный размер кучи, а затем максимальный размер кучи. Мы хотим сохранить небольшой размер кучи, чтобы заставить GC быть более активным. Наконец, мы решаем использовать CMS — сборщик мусора Concurrent Mark and Sweep. Пришло время запустить наше приложение!

Во-первых, давайте попробуем заполнить штатное пространство . Введите заливку.

Мы можем исследовать наш файл gclog.log , чтобы увидеть, что произошло. Мы увидим около 15 коллекций. Строка, зарегистрированная для отдельных коллекций, выглядит так:

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs] 
168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

Как видим, память заполнена.

Затем давайте принудительно вызовем System.gc() , набрав gc . Мы видим, что использование памяти существенно не изменилось:

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs] 
120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs]
[Times: user=0.27 sys=0.00, real=0.26 secs]

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

Давайте очистим кеш , набрав invalidate . Мы должны увидеть, что в файле gclog.log больше нет строк журнала .

Мы можем попытаться заполнить кеш еще несколько раз, но GC не происходит. Это момент, когда мы можем перехитрить сборщик мусора . Теперь, после форсирования GC, мы увидим строку вида:

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs] 
103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs]
[Times: user=0.10 sys=0.00, real=0.10 secs]

Мы освободили внушительный объем памяти! Но так ли это было необходимо прямо сейчас? Что случилось?

Согласно этому примеру вызов System.gc() может показаться заманчивым, когда мы освобождаем большие объекты или аннулируем кэши.

5. Другое использование

Существует очень мало причин, по которым явный вызов метода System.gc() может оказаться полезным.

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

Другой — анализ утечек памяти это скорее практика отладки, чем то, что мы хотели бы сохранить в рабочем коде. Вызов System.gc() и обнаружение того, что объем кучи по-прежнему велик, может указывать на утечку памяти .

6. Резюме

В этой статье мы исследовали метод System.gc() и то, когда он может показаться полезным.

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

Как обычно, код, использованный в этой статье, можно найти на GitHub .