Основы настройки GC в Java 9

Java

После нескольких задержек Java 9, наконец, выпущена через год после первоначально запланированной даты. Java 9 вводит множество новых функций, помимо ослепленияModule SystemиREPL, я думаю, что самое важное изменение заключается в том, что стандартный GC (сборщик мусора) заменяется на новое поколение G1 (Garbage-First), которое является более сложным, комплексным и имеет лучшую производительность. Сопровождающие JDK всегда были консервативны в своем выборе GC.G1 был в поле зрения разработчиков с эпохи JDK 1.6, и он прошел долгий путь с тех пор, как сегодня официально стал GC Hotspot по умолчанию.

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

  1. Объем GC
  2. За что отвечает ГК
  3. 4 вида GC в JVM
  4. Некоторые детали G1
  5. Тестирование G1 с официальной версией Java 9
  6. Некоторые простые методы настройки GC

Во-первых, объем GC

Чтобы говорить о масштабах GC, мы должны сначала поговорить о структуре памяти JVM, в основном в памяти JVM есть следующие области: куча, область методов (называется в спецификации JVM, Hotspot примерно соответствует Metaspace), стек , локальный стек методов, ПК и т. д., где GC в основном действует на куче, как показано на следующем рисунке:

JVM内存结构
Структура памяти JVM

Среди них куча и области методов являются общими для всех потоков, а остальные уникальны для потоков.HotSpot JVM использует механизм сборки мусора на основе поколений, поэтому куча делится на несколько разных областей (в G1 каждое возрастное поколение выполняет не то есть непрерывный цельный кусок памяти.Для удобства описания здесь также используется традиционный способ представления), как показано на следующем рисунке:

JVM堆中的分区
Разделы в куче JVM

2. За что отвечает ГК

Разработка GC идет шаг за шагом с разработкой JDK (Standard Edition).Можно сказать, что сборка мусора является наиболее влияющим на производительность поведением в JDK. Грубо говоря, сборщик мусора «управляет памятью, чтобы гарантировать, что программа может нормально использовать память, когда памяти достаточно». В частности, GC обычно делает следующие три вещи:

1. Назначение и управление возрастом объектов

Вообще говоря, GC должен управлять процессом «распределения объектов в молодом поколении (Young) на приведенном выше рисунке, а затем уничтожать или продвигать их в старое поколение (Tenured) посредством серии управления возрастом». Этот процесс будет сопровождаться несколькими второстепенными GC.

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

Однако иногда объект создается непосредственно в старом поколении, о чем будет сказано ниже.

2. Маркировка в старом поколении

Алгоритм GC старости можно грубо рассматривать как алгоритмы mark-compact (mark-compact, который на самом деле является смесью mark-clean, mark-copy и mark-sort), поэтому первое, что нужно сделать для очистки мусора в старости заключается в очистке в старых s для выживших объектов (анализ достижимости, для различной достижимости может относиться кДеструктуризация JDK — реализация ссылок и динамических прокси в Java), чтобы отметить, что для серверных приложений, которым требуется высокая пропускная способность, этот процесс часто должен быть параллельным.

Процесс маркировки происходит после запуска основного GC, и разные GC имеют разные условия запуска для основного GC и реализации процесса маркировки.

3. Компрессия в старом поколении

На основе предыдущего пункта происходит сжатие уцелевших объектов (поведение CMS и G1 несколько отличается).Процесс сжатия заключается в копировании уцелевших объектов по одному из начальной точки старости, чтобы старый возраст поддерживается на уровне непрерывной памяти, устранение фрагментации памяти значительно поможет повысить скорость выделения памяти.

3. Типы ГК

Точка доступа разделит хост на класс клиента или класс сервера в соответствии с характеристиками оборудования и типом операционной системы хоста.Если это хост серверного типа, перед Java 9 по умолчанию используется Parallel GC.Используется Java 9 G1 по умолчанию. Критерии выбора хостов серверного типа: «количество ядер ЦП больше 1, а объем памяти больше 2 ГБ», поэтому большинство хостов теперь можно считать хостами серверного типа.

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

1. Serail

Serail — это самый ранний сборщик мусора, он использует только один поток для выполнения всех второстепенных и основных сборок мусора. Пока он работает, все остальное приостанавливается. Он работает очень просто: в безопасной точке, где требуется GC, он останавливает все остальные потоки (Stop-The-World), помечая копирование молодого поколения или перетасовывая пометки старого поколения.

Можно использовать аргументы JVM-XX:+UseSerialGCЧтобы включить этот GC, при использовании этого параметра и молодое поколение, и старое поколение будут использовать Serial для сборки мусора. Используйте алгоритм пометки-копии в молодом поколении, чтобы скопировать уцелевшие объекты в Эдеме и уцелевшие объекты в непустой области Suvivor (From) в пустую область Suvivor (To), и в то же время продвигать некоторые объекты в Suvivor до старости идти. В пожилом возрасте используется алгоритм маркировки-сопоставления.

Кажется, что Serial старый и рудиментарный, но когда ресурсы хоста ограничены или куча JVM мала (например, размер памяти кучи меньше 100 МБ), Serial может достичь лучших результатов, потому что другие параллельные или параллельные сборщики мусора основаны в многопоточном режиме это приведет к дополнительному переключению потоков и накладным расходам на межпотоковое взаимодействие.

2. Parallel/Throughput

До Java 9 Parallel был сборщиком мусора по умолчанию для JVM на хостах серверов.Его алгоритм сборки мусора в основном такой же, как и у Serial, за исключением того, что он использует для выполнения несколько потоков. За счет использования многопоточности вы можете пользоваться преимуществами многоядерности процессора, которые можно указать явно через параметры -XX:+UseParallelGC -XX:+UseParallelOldGC.

3. CMS

И CMS, и G1 относятся к «в основном параллельному сборщику мусора пометки и очистки», который можно открыть с помощью параметра -XX:+UseConcMarkSweepGC. Сборщик мусора молодого поколения CMS используетParallel NewДля этого его поведение почти такое же, как и в Parallel, но есть некоторые отличия в их реализации, например, Parallel может автоматически регулировать размер каждой области в молодом поколении, используя поиск в ширину и т.д.

Старость использует CMS, и переработка CMS в основном аналогична Parallel, разница в том, что CMS использует более сложные этапы анализа доступности, а не выполняет сжатие каждый раз, поэтому достигается эффект, Stop-The Продолжительность - Мир уменьшится, и время прерывания JVM уменьшится, что подходит для использования в сценариях, чувствительных к задержке.

CMS устарела в Java 9, но понимание поведения CMS поможет вам понять G1, поэтому здесь будет кратко описано. Шаги CMS примерно следующие:

  1. первая отметка
    Начиная с GC Roots, найдите свои первые достижимые объекты в старом поколении, на эти объекты либо напрямую ссылаются GC Roots, либо ссылаются GC Roots через объекты в молодом поколении. Этот шаг остановит-Мир.

  2. одновременная маркировка
    На основе первой маркировки выполняется дальнейший анализ доступности для маркировки уцелевших объектов. Этот шаг называется «параллельной» маркировкой, поскольку поток маркировки выполняется одновременно с рабочим потоком приложения, то есть этот шаг не остановит мир.

  3. вторая отметка
    В процессе параллельной маркировки, поскольку программа все еще выполняется, достижимость некоторых объектов изменится после завершения параллельной маркировки, поэтому их нужно будет снова пометить. Этот шаг остановит-Мир.

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

Дизайн CMS более сложен, поэтому он также приносит некоторые проблемы, такие как плавающий мусор (плавающий мусор, который относится к объекту, достижимому на первом шаге, но недостижимому при выполнении второго шага), потому что он не делать Сжатие в пожилом возрасте приводит к большей фрагментации памяти в пожилом возрасте.

4. G1

Из-за «введения параллельной маркировки» и «отсутствия сжатия в старом возрасте» CMS может улучшить показатели задержки ответа, но также создает некоторые проблемы. Сама G1 выступает как заменитель CMS, в сценарии ее использования куча больше не делится непрерывно на различные упомянутые выше поколения, а вся куча будет делиться на регионы (Regions), каждый регион A может быть любым поколением. Как показано ниже:

使用G1的JVM某时刻的堆内存
Куча памяти в определенный момент JVM с использованием G1

Среди них красный прямоугольник — это молодое поколение (обозначен буквой S — это область выживших, остальные — Эдем), а другие нижние синие области — это старое поколение (обозначена буквой H — это область больших объектов, используемая для хранения больших предметов).

В-четвертых, некоторые детали G1

G1 такой же, как три вышеуказанных сборщика мусора, а также является сборщиком мусора поколений. Этапы сборки мусора можно разделить на фазу только для молодых (аналогично Minor GC) и фазу смешанной сборки мусора (этап восстановления пространства). На следующем рисунке представлена ​​схема этих двух этапов в документации Oracle:

jsgct_dt_001_grbgcltncyl.png
jsgct_dt_001_grbgcltncyl.png

Цели дизайна G1 и применимые объекты

Цель разработки G1 — позволить большим виртуальным машинам JVM динамически управлять поведением сборщика мусора для достижения заданных пользователем целей производительности. G1 сбалансирует пропускную способность и задержку ответа на основе:насколько это возможнодля удовлетворения потребностей пользователей. JVM, для которой он подходит, имеет следующие характеристики:

  1. Размер кучи может достигать десятков гигабайт (и более), количество живых объектов также велико.
  2. Распределение объектов и поведение старения изменяются по мере выполнения программы
  3. Мусор может легко образоваться на куче
  4. Требуется меньше времени паузы Stop-The-World, обычно менее нескольких сотен миллисекунд.
Проверьте поведение G1

Если вы хотите увидеть конкретный процесс выполнения сборки мусора, вы можете использовать параметры виртуальной машины-Xlog:gc*=debugили-Xlog:gc*=info, предыдущий распечатает больше деталей. Остерегайтесь традиционных параметров ВМ-XX:+PrintGCDetailsБыл заброшен в Java9, будет предупреждающая информация. Вы можете использовать программу в следующем коде для тестирования:

static int TOTAL_SIZE = 1024 * 5;
static Object[] floatingObjs= new Object[TOTAL_SIZE];
static LinkedList<Object> immortalObjs = new LinkedList<Object>();
//释放浮动垃圾
synchronized static void renewFloatingObjs() {
    System.err.println("存活对象满========================================");
    if (floatingSize + 5 >= TOTAL_SIZE) {
        floatingObjs= new Object[TOTAL_SIZE];
        floatingSize = 0;
    }
}
//添加浮动垃圾
synchronized static void addObjToFloating(Object obj) {
    if (floatingSize++ < TOTAL_SIZE) {
        floatingObjs[floatingSize] = obj;
        if (immortalSize++ < TOTAL_SIZE) {
            immortalObjs.add(obj);
        } else {
            immortalObjs.remove(new Random().nextInt(TOTAL_SIZE));
            immortalObjs.add(obj);
        }
    }
}

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Byte[] garbage = new Byte[1024 * (1 + new Random().nextInt(20))];
                if (new Random().nextInt(20) < 2) {
                    if (floatingSize + 5 >= TOTAL_SIZE) {
                        renewFloatingObjs();
                    }
                    addObjToFloating(garbage);
                }
            }
        }).start();
    }
}

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

Из приведенного выше теста вы можете получить следующий журнал GC 1. Это полный GC молодого поколения.Из него видно, что размер области по умолчанию составляет 1M, и он запустится один разFull GC, что примерно в виде[<虚拟机运行的时长>][<日志级别>][<标签>] GC(<GC的标识>) <其他信息>

//日志1
[0.014s][info][gc,heap] Heap region size: 1M
//一次完整的年轻代垃圾回收,伴随着一次暂停
[12.059s][info ][gc,start             ] GC(18) Pause Young (G1 Evacuation Pause)                            
[12.059s][info ][gc,task              ] GC(18) Using 8 workers of 8 for evacuation                            
[12.078s][info ][gc,phases            ] GC(18)   Pre Evacuate Collection Set: 0.0ms                            
[12.078s][info ][gc,phases            ] GC(18)   Evacuate Collection Set: 18.6ms                            
[12.079s][info ][gc,phases            ] GC(18)   Post Evacuate Collection Set: 0.3ms                            
[12.079s][info ][gc,phases            ] GC(18)   Other: 0.3ms                            
[12.079s][info ][gc,heap              ] GC(18) Eden regions: 342->0(315)                            
[12.079s][info ][gc,heap              ] GC(18) Survivor regions: 38->35(48)                            
[12.079s][info ][gc,heap              ] GC(18) Old regions: 425->463                            
[12.079s][info ][gc,heap              ] GC(18) Humongous regions: 0->0                            
[12.078s][debug][gc,ergo,ihop         ] GC(18) Request concurrent cycle initiation (occupancy higher than threshold) occupancy: 485490688B allocation request: 0B threshold: 472331059B (45.00) source: end of GC
[12.078s][debug][gc,ihop              ] GC(18) Basic information (value update), threshold: 472331059B (45.00), target occupancy: 1049624576B, current occupancy: 521069456B, recent allocation size: 20640B, recent allocation duration: 817.38ms, recent old gen allocation rate: 25251.50B/s, recent marking phase length: 0.00ms
[12.078s][debug][gc,ihop              ] GC(18) Adaptive IHOP information (value update), threshold: 472331059B (47.37), internal target occupancy: 997143347B, occupancy: 521069456B, additional buffer size: 367001600B, predicted old gen allocation rate: 318128.08B/s, predicted marking phase length: 0.00ms, prediction active: false
[12.078s][debug][gc,ergo,refine       ] GC(18) Updated Refinement Zones: green: 15, yellow: 45, red: 75
[12.079s][info ][gc,heap              ] GC(18) Eden regions: 342->0(315)
[12.079s][info ][gc,heap              ] GC(18) Survivor regions: 38->35(48)
[12.079s][info ][gc,heap              ] GC(18) Old regions: 425->463
[12.079s][info ][gc,heap              ] GC(18) Humongous regions: 0->0
[12.079s][info ][gc,metaspace         ] GC(18) Metaspace: 5172K->5172K(1056768K)
[12.079s][debug][gc,heap              ] GC(18) Heap after GC invocations=19 (full 0):        
[12.079s][info ][gc                   ] GC(18) Pause Young (G1 Evacuation Pause) 803M->496M(1001M) 19.391ms                                
[12.079s][info ][gc,cpu               ] GC(18) User=0.05s Sys=0.00s Real=0.02s            
Переработка только для молодых

Для чистой рециркуляции молодого поколения алгоритм очень прост, что очень похоже на молодое поколение Parallel и CMS.Это многопоточный параллельный процесс выполнения, который также требует Stop-The-World (соответствует приведенному выше логу в журнал).Pause Young), остановите все рабочие потоки, а затем скопируйте уцелевшие объекты в Eden в область Suvivor, где многие объекты будут скопированы из нескольких разных областей в несколько областей, поэтому этот шаг называется эвакуацией в G1 (Эвакуация), а в в то же время продвигайте объекты на Suvivor, достигшие возрастного порога, в область старости.

Переработка старого поколения (параллельный цикл)

Переработка старого поколения G1 происходит после того, как пространство старого поколения достигает порога (Initiating Heap Occupancy Percent).Эта переработка сопровождается работой по переработке молодого поколения, но несколько отличается от упомянутой выше переработки.

  1. Переработка мусора молодого поколения: Наряду с работами по переработке мусора молодого поколения, одновременно будут выполняться параллельная маркировка и часть работ по очистке, чтобы можно было разделить Stop-The-World сбора мусора молодого поколения.

    1. Первая отметка: соответствует один разPause Initial Mark
      Аналогично шагам для CMS, сначала выполняется первая разметка. Но есть большая разница в методах реализации: G1 сначала делает виртуальный снимок (Snapshot-At-The-Beginning) объектов текущей кучи, а затем по этому снимку помечает объекты и регионы старого поколения, и выполняет сборку мусора. После этого будет параллельный процесс маркировки наподобие CMS.
      Это вызовет проблему.После того, как эта коллекция будет завершена, доступность некоторых объектов будет изменена во время параллельного процесса маркировки, что приведет кОбъекты, которые уже недоступны, по-прежнему не собираются. Но это приводит к лучшему времени отклика.

    2. Relabel: переписка один разPause Remark
      На этом этапе G1 сначала завершает работу по маркировке, начатую на предыдущем шаге, а затем обрабатывает специально указанные объекты (подробности см.Деструктуризация JDK — реализация ссылок и динамических прокси в Java) и сборка мусора в области метапространства. Этот шаг будет Stop-The-World.

    3. Очистить: написать один разPause Cleanup
      Основная цель этого шага — собрать информацию об области памяти в текущей куче, освободить пустую область и выполнить некоторую подготовительную работу для следующего освобождения пространства.После очистки обычно происходит восстановление молодого поколения.Для освобождения пространства , он войдет в работу мелиорации следующего молодого поколения. Этот шаг будет Stop-The-World.
    1. Гибридная сборка мусора: один или несколько разPause Mixed
      Основная задача заключается в эвакуации региональной памяти старого поколения (Эвакуация), а также включает в себя региональную работу по утилизации молодого поколения. В то же время этот шаг также динамически настраивает IHOP.

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

выделение крупных объектов

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

Для G1 в большинстве случаев объекты будут размещены в Eden.Если JVM определяет, что объект является большим объектом (пороговое значение может быть установлено с помощью -XX:G1HeapRegionSize), он будет непосредственно размещен в области больших объектов, такой как старое поколение.

Для других смежных GC областей памяти следующий процесс выделения объектов, переданных из StackOverflow в куче:

  1. Используя локальный буфер выделения потока (TLAB), если места достаточно, выделение выполняется успешно.
    Как видно из названия, TLAB является потоко-эксклюзивным, поэтому потокобезопасен и очень быстр. Если TLAB заполнен, потоку выделяется новый TLAB.

  2. Если места в TLAB недостаточно для размещения объекта в этот раз, но свободного места еще много, TLAB не используется, и объект размещается непосредственно в Eden.
    Выделение объектов непосредственно в Eden для вытеснения операций с указателями в Eden обходится дороже, чем использование TLAB.

  3. Если выделение объекта Eden не удается, инициируется Minor GC.

  4. Если Minor GC оказывается недостаточным, он передается непосредственно старому поколению.

Некоторые простые методы настройки GC

1. Используйте другой индексный объект

Тип ссылки будет напрямую влиять на поведение GC объекта, на который ссылаются.При выполнении некоторых приложений, чувствительных к памяти, вы можете ссылаться на соответствующий тип ссылки. Для получения подробной информации см.Деструктуризация JDK — реализация ссылок и динамических прокси в Java.

2. Параллельное использование

Как видно из вышеизложенного, GC по умолчанию в Java 8 — Parallel, который также называется Throughput, поэтому его цель — максимально увеличить пропускную способность системы. В Parallel вы можете настроить максимальное время остановки (-XX:MaxGCPauseMillis, по умолчанию не задано) и пропускную способность (-XX:GCTimeRatio, значение по умолчанию — 99, то есть 1% времени используется для сборки мусора) на настройка по параметрам Поведение сборщика мусора. Среди них установка максимального времени остановки может привести к тому, что сборщик мусора будет корректировать размер каждого раздела генерации возраста (реализовано приращениями).

3. Использование G1

Начиная с Java 9 G1 стал сборщиком мусора по умолчанию. В G1 есть некоторые подробные концепции, которые не были описаны выше. Позвольте мне сначала представить их:

  1. Запомненные наборы (Rsets): для каждой области существует набор, который записывает все ссылки в этой области.
    1. Уточнение G1: в G1 должна быть серия потоков, которые постоянно поддерживают Rsets.
    2. Наборы сбора (Csets): области, которые необходимо очистить во время сборки мусора, и доступные объекты (живые объекты) в этих областях будут эвакуированы. Эти регионы могут относиться к любому возрастному поколению.
    3. Барьеры записи: для каждой операции присваивания G1 будет иметь два барьера записи, один перед записью (Pre-Write) и один после записи (Post-Write). Предварительная запись в основном связана с SATB, а пост-запись в основном связана с Rsets.
    4. Очередь грязных карт: барьер записи будет помещать записанные записи в эту очередь, и будут потоки, которые постоянно сбрасывают объекты здесь в Rsets.
    5. Зеленая/желтая/красная зона: три порога, влияющие на количество потоков, обрабатывающих очередь грязных карт. В зависимости от количества элементов в очереди грязных карточек могут быть установлены некоторые режимы GC (это можно рассматривать как логическое разделение очереди грязных карточек на несколько областей). Зеленый цвет означает, что при превышении порога будет создан новый поток для обработки очереди Желтый цвет означает, что при превышении порога эти потоки будут принудительно запущены Красный цвет означает, что при превышении порога поток из операция записи сама выполнит уточнение G1.

G1 предоставляет множество настраиваемых параметров для различных целей, список выглядит следующим образом:

параметр описывать
-XX:+G1UseAdaptiveConcRefinement, Отрегулируйте ресурсы, используемые уточнением G1.
-XX:G1ConcRefinementGreenZone=, Отрегулируйте ресурсы, используемые уточнением G1.
-XX:G1ConcRefinementYellowZone=, Отрегулируйте ресурсы, используемые уточнением G1.
-XX:G1ConcRefinementRedZone=, Отрегулируйте ресурсы, используемые уточнением G1.
-XX:G1ConcRefinementThreads= Отрегулируйте ресурсы, используемые уточнением G1.
-XX:G1RSetUpdatingPauseTimePercent=10 Отрегулируйте пропорцию времени, необходимого для уточнения G1, ко всему времени сборки мусора, G1 будет динамически корректировать параметры первой строки в соответствии с этим временем.
-XX:+ReduceInitialCardMarks Пакетное выполнение генерации объектов для сокращения времени начальной маркировки
-XX:-ParallelRefProcEnabled Используйте многопоточность для обработки ссылок на этапе перемаркировки, как описано выше.
-XX:G1SummarizeRSetStatsPeriod= После установки n сборок мусора распечатайте сводный отчет Rsets.
-XX:GCTimeRatio= Установить пропускную способность GC. Общее время, которое должен использовать GC, составляет 1/(1 + n), этот параметр повлияет на рост размера поколений разного возраста.
-XX:G1HeapRegionSize Установить размер области

Основные справочные документы:

  1. Getting Started with the G1 Garbage Collector
  2. Garbage-First Garbage Collector Tuning
  3. Evaluating and improving remembered sets in the HotSpot G1 garbage collector
  4. G1GC Internals
  5. GC Algorithms: Basics
  6. Различие между несколькими пулами констант в Java,