1. Пишите впереди
| В этой статье в основном обобщаются некоторые сценарии использования комбинации «CMS + ParNew» в Hotspot VM. Основное внимание уделяется анализу основной причины и обобщению метода устранения неполадок с помощью части исходного кода. Процесс устранения неполадок будет опущен. Кроме того, в этой статье много профессиональных терминов и существуют определенные пороги чтения. не ясно, пожалуйста, обратитесь к соответствующим материалам.
| Общее количество слов около 20 000 (исключая фрагменты кода), а общее время чтения около 30 минут.Статья длинная, и вы можете выбрать интересующую вас сцену для исследования.
1.1 Введение
С тех пор, как Sun выпустила язык Java, технология GC используется для автоматического управления памятью, что позволяет избежать проблемы с висячим указателем, вызванной ручным управлением, и значительно повышает эффективность разработки.С тех пор технология GC также стала известна одним махом. Сборщик мусора имеет очень долгую историю. В 1960 году Джон Маккарти, известный как «отец Лиспа» и «отец искусственного интеллекта», опубликовал в своей статье алгоритм сборки мусора. За последние 60 лет развитие технологии сборки мусора также продвигаются семимильными шагами, но будь то какими бы передовыми не были сборщики, они основаны на сочетании или применении трех базовых алгоритмов, а это значит, что фундаментальная задача, которую предстоит решать GC, не меняется уже столько лет . Автор считает, что в не столь отдаленном будущем технология ГХ все же не устареет, по сравнению с новой технологией, меняющейся с каждым днем, ГХ является классической технологией, более достойной нашего изучения.
В настоящее время данные GC Java в Интернете либо в основном объясняют теорию, либо анализируют проблему GC отдельной сцены, а информации по всей системе очень мало. Урок из прошлого, учитель из будущего, несколько инженеров из Meituan собрали аналитические статьи по различным внутренним проблемам GC и сделали некоторые выводы, основанные на их личном понимании, в надежде сыграть роль во «внедрении новых идей». Если в статье есть ошибки, пожалуйста, исправьте меня.
Можно ли систематически осваивать возможности решения проблем GC? Некоторыми влияющими факторами являютсявзаимная причина и следствиеКак проанализировать проблему? Например, если RT службы внезапно возрастает, есть четыре симптома: увеличение времени сборки мусора, увеличение количества блоков потоков, увеличение количества медленных запросов и высокая загрузка ЦП. Как судить, есть ли проблема с GC? Каковы общие проблемы с использованием CMS? Как определить, в чем первопричина? Как решить или избежать этих проблем? Я полагаю, что после прочтения этой статьи у вас будет систематическое понимание обработки проблем CMS GC, и вы сможете с легкостью решать эти проблемы.
1.2 Обзор
Если вы хотите систематически осваивать решение проблем GC, автор дает путь обучения здесь, Структура всей статьи также разработана в соответствии с этой структурой, которая в основном разделена на четыре этапа.
-
Сформируйте базу знаний:От структуры памяти JVM до алгоритмов и сборщиков мусора — изучите основы GC и освойте некоторые распространенные инструменты анализа проблем GC.
-
Определить показатели оценки:Разберитесь в методах оценки базового GC, узнайте, как устанавливать индикаторы независимых систем и определите, есть ли проблема с GC в бизнес-сценариях.
-
Практика настройки сцены:Используйте индикаторы знаний и оценки системы для анализа и решения распространенных сценариев проблем GC в девяти CMS.
-
Обобщим опыт оптимизации:Подведите итоги общего процесса и выдвиньте некоторые предложения автора, а заодно улучшите обобщенный опыт в систему знаний.
2. Основы ГХ
Перед официальным началом сделайте краткий обзор и познакомьте с общими понятиями, такими как разделение памяти JVM, алгоритм сбора, сборщик и т. д. Учащиеся с более глубокими знаниями могут сразу пропустить эту часть.
2.1 Основные понятия
-
ГК:GC сам по себе имеет три семантики, и следующие должны привносить разные семантики в соответствии с конкретными сценариями:
-
Garbage Collection: Технология сбора мусора, сущ.
-
Garbage Collector: сборщик мусора, существительное.
-
Garbage Collecting: действие по сбору мусора, глагол.
-
-
Мутатор:Роль-производитель мусора, наше приложение, создатель мусора, выделяет и освобождает через Распределитель.
-
TLAB:Сокращение для Thread Local Allocation Buffer, эксклюзивные потоки на основе CAS (Mutator Threads) могут предпочтительно выделять объекты в части памяти в Eden, поскольку в области памяти, эксклюзивной для потоков Java, нет конкуренции блокировок, поэтому скорость выделения выше, каждый TLAB является эксклюзивным для одного потока.
-
Карточный стол:В китайском переводе это таблица карточек, которая в основном используется для обозначения состояния страницы карточек, и каждый элемент таблицы карточек соответствует странице карточек. Когда ссылка на объект на странице карты имеет операцию записи, барьер записи изменит состояние таблицы карт, в которой находится отмеченный объект, на грязное.Суть таблицы карт состоит в том, чтобы решить проблему ссылок между поколениями. Как это решить, можно обратиться к этому вопросу на StackOverflowhow-actually-card-table-and-writer-barrier-works, или изучите исходный код в cardTableRS.app.
2.2 Разделение памяти JVM
Как видно на официальном сайте JCP (Java Community Process), последняя версия Java достигла уровня Java 16. Будущая версия Java 17 и текущая версия Java 11 и Java 8 являются версиями LTS. Спецификация JVM также меняется с итерациями. В этой статье в основном обсуждается CMS, вот структура памяти Java 8.
Сборщик мусора в основном работает в области Heap и области MetaSpace (синяя часть на рисунке выше), в Direct Memory, если используется DirectByteBuffer, то при нехватке выделенной памяти сборщик мусора пропускаетCleaner#clean
косвенное управление.
Шаги, с которыми сталкивается любая система автоматического управления памятью: выделение места для новых объектов, затем сбор места для мусорных объектов, давайте расширим основы.
2.3 Размещение объектов
Операция с адресом объекта в Java в основном использует Unsafe для вызова методов выделения и освобождения C. Существует два метода выделения:
-
Бесплатный список (бесплатный список):Запись свободных адресов через дополнительное хранилище превращает случайный ввод-вывод в последовательный ввод-вывод, но приводит к дополнительному потреблению места.
-
указатель удара:Разделив точку как указатель, память должна быть выделена, только указатель на свободный конец движущегося объекта равен размеру расстояния, более высокой эффективности распределения, но ограниченным сценариям использования.
2.4 Объекты коллекции
2.4.1 Идентификация мусора
-
Подсчет ссылок:Подсчитывается ссылка на каждый объект. Всякий раз, когда есть место для ссылки на него, счетчик равен +1, а ссылка недействительна на -1. Счетчик ссылок помещается в заголовок объекта. Объекты больше 0 считаются живыми объекты. Хотя проблема циклических ссылок может быть решена с помощью алгоритма Recycler, в многопоточной среде изменения счетчика ссылок также требуют выполнения дорогостоящих операций синхронизации, а производительность низкая.Ранние языки программирования будут использовать этот алгоритм.
-
Анализ достижимости, также известный как Tracing GC:Поиск объекта начинается с GC Root.Объект, который можно искать, является достижимым объектом.В данный момент недостаточно судить о том,живой/мертвый ли объект.Его необходимо несколько раз пометить для более точного определения , Объекты за пределами всего связного графа могут быть переработаны как мусор. В настоящее время основные виртуальные машины на Java используют этот алгоритм.
Примечание: метод подсчета ссылок может справиться с проблемой циклических ссылок, не повторяйте это в следующем интервью~~
2.4.2 Алгоритм сбора
Поскольку с появлением автоматического управления памятью существовали некоторые алгоритмы сбора, разные сборщики также комбинируются в разных сценариях.
-
Марк-развертка:Процесс восстановления в основном делится на два этапа: первый этап — этап трассировки, то есть граф объектов просматривается из корня GC, и каждый встречающийся объект помечается (Mark), а второй этап — этап Sweep. , то есть сборщик проверяет каждый объект в куче и перерабатывает все неотмеченные объекты без движения объекта в течение всего процесса. Весь алгоритм будет использовать Tricolor Abstraction, BitMap и другие технологии в различных реализациях для повышения эффективности алгоритма, и он более эффективен, когда есть много выживших объектов.
-
Марк-Компакт:Основная цель этого алгоритма - решить проблему фрагментации, которая существует в немобильных сборщиках, он также разделен на два этапа. Первый этап аналогичен Mark-Sweep, а второй этап будет сортировать уцелевшие объекты в соответствии с порядок сортировки (Compaction Order) для сортировки. Основные реализации включают алгоритм восстановления с двумя пальцами, алгоритм скользящего восстановления (Lisp2) и алгоритм многопоточного сжатия.
-
Копирование:Разделите пространство на две половины одинакового размера, От и До, только одна из них будет использоваться одновременно, а уцелевшие объекты в одной половине области будут копироваться в другую половину области каждый раз, когда производится сбор. Существуют рекурсивные (предложенные Робертом Р. Фенихелем и Джеромом С. Йохельсоном) и итерационные (предложенные Чейни) алгоритмы, а также приближенные алгоритмы первого поиска, решающие проблемы рекурсивных стеков, строк кэша и т. д. для первых двух. Алгоритм копирования может быстро выделять память, сталкиваясь с указателями, но у него также есть недостаток, заключающийся в низком использовании пространства.Кроме того, стоимость копирования относительно высока, когда уцелевший объект относительно велик.
Некоторые сравнения трех алгоритмов с точки зрения того, следует ли перемещать объекты, пространство и время, при условии, что количество выживших объектов равноL, размер кучиH,но:
Соединяя трудоемкие действия пометки, развертки, уплотнения и копирования, получается примерно следующее соотношение:
Хотя и сжатие, и копирование включают перемещение объектов, в зависимости от алгоритма сжатие может вычислить целевой адрес объекта один раз, зафиксировать указатель, а затем переместить объект. Копирование может выполнять эти действия одновременно, поэтому оно может быть быстрее. Кроме того, также необходимо обратить внимание на накладные расходы, вызванные сборщиком мусора, причем не только сборщик, но и распределитель. Если вы можете гарантировать, что память не фрагментирована, выделение может быть выполнено с помощью перестановки указателей, для чего достаточно переместить указатель, чтобы завершить выделение, что происходит очень быстро. А если память фрагментирована, ею приходится управлять таким способом, как freelist, и скорость выделения обычно медленнее.
2.5 Коллекционеры
В настоящее время в Hotspot VM есть две основные категории коллекции поколений и секционированной коллекции.Подробности см. на рисунке ниже, но в будущем она будет постепенно развиваться в секционированную коллекцию. Внутри Meituan некоторые компании пытаются использовать ZGC (заинтересованные студенты могут изучить эту статью).Исследование и практика сборщика мусора нового поколения ZGC), а остальные в основном остаются на CMS и G1. Кроме того, после JDK11 для анализа производительности предоставляется сборщик Epsilon (безоперационный сборщик мусора), который не выполняет никаких действий по сборке мусора. Другой — Zing JVM от Azul, чей сборщик C4 (Concurrent Continuous Compacting Collector) также имеет определенное влияние в отрасли.
Примечания: Стоит упомянуть, что в первые годы на Azul также работал евангелист технологии GC в Китае RednaxelaFX (известный как R big в реках и озерах), и некоторые материалы в этой статье также относятся к некоторым из его статьи.
2.5.1 Коллекторы поколений
-
ПарНовый:Многопоточный сборщик, использующий алгоритм репликации и в основном работающий в районе Юнга.
-XX:ParallelGCThreads
Параметры для управления количеством собранных потоков, весь процесс STW, часто используется в сочетании с CMS. -
CMS:С целью получения кратчайшего времени паузы восстановления используется алгоритм «маркировка-зачистка» для выполнения сборки мусора в 4 больших шага.Первоначальная маркировка и повторная маркировка будут STW, и большинство из них используются на стороне сервера. Интернет-сайтов или систем B/S JDK9 Помечен как устаревший, JDK14 удален, см. подробностиJEP 363.
2.5.2 Сборщик разделов
-
Г1:Сборщик мусора на стороне сервера, применяемый в многопроцессорной среде с большим объемом памяти, обеспечивает высокую пропускную способность при максимально возможном соблюдении требований к времени паузы сборки мусора.
-
ЗГК:Сборщик мусора с малой задержкой, запущенный в JDK11, подходит для управления памятью и повторного использования больших объемов памяти с малой задержкой, тест производительности SPECjbb 2015, в куче 128G, максимальное время паузы составляет всего 1,68 мс, что намного лучше, чем G1 и CMS.
-
Шенандоа:Разработанный командой Red Hat, аналогичный G1, это сборщик мусора, основанный на дизайне регионов, но не требующий Remember Set или Card Table для записи межрегиональных ссылок, а время паузы не имеет ничего общего с размером куча. Время паузы близко к ZGC, и на следующем рисунке показан бенчмарк с такими сборщиками, как CMS и G1.
2.5.3 Общие коллекторы
В настоящее время наиболее часто используемыми сборщиками являются сборщики CMS и G1, оба из которых имеют концепцию генерации.Основные структуры памяти следующие:
2.5.4 Другие коллекторы
Выше перечислены только общие сборщики, их гораздо больше, например, сборщики в реальном времени, такие как Metronome, Stopless, Staccato, Chicken, Clover и т. д., сборщики с параллельным копированием/сопоставлением, такие как Sapphire, Compressor, Pauseless, Doligez-Leroy- Контье и т. д. Сортировка тегов и переработчик не будут представлены здесь по отдельности из-за недостатка места.
2.6 Общие инструменты
Если вы хотите сделать хорошую работу, вы должны сначала заточить свои инструменты.Вот некоторые инструменты, обычно используемые автором.Вы можете свободно выбирать конкретную ситуацию.Все проблемы в этой статье обнаружены и проанализированы с использованием этих инструментов.
2.6.1 Терминал командной строки
- Стандартные классы терминалов: jps, jinfo, jstat, jstack, jmap
- Класс интеграции функций: jcmd, vjtools, arthas, greys
2.6.2 Визуальный интерфейс
- Легко: JConsole, JVisualvm, HA, GCHisto, GCViewer
- Дополнительно: MAT, JProfiler
Arthas рекомендуется для командной строки, JProfiler рекомендуется для визуального интерфейса, а также есть некоторые онлайн-платформы.gceasy,heaphero,fastthread, Внутренний скальпель Meituan (собственно разработанный инструмент диагностики проблем JVM, в настоящее время не с открытым исходным кодом) также относительно прост в использовании.
3. Решение проблемы GC
Прежде чем приступать к устранению неполадок и оптимизации сборщика мусора, нам необходимо выяснить, вызвана ли проблема непосредственно сборщиком мусора или исключение сборщика мусора вызвано кодом приложения, и, наконец, проблема возникает.
3.1 Есть ли проблемы с оценкой GC?
3.1.1 Установка критериев оценки
Два основных показателя для оценки GC:
-
Задержка:Его также можно понимать как максимальное время паузы, то есть наибольшее время для STW в процессе сборки мусора.Чем короче, тем лучше, увеличение частоты можно принять в определенной степени, и основное направление развития GC технологии.
-
Пропускная способность:В течение жизненного цикла прикладной системы, поскольку поток GC будет занимать доступные в данный момент такты ЦП мутатора, пропускная способность представляет собой процент времени, эффективно затраченного мутатором, от общего времени работы системы.Например, система работает 100 минут, а сборщик мусора занимает 1 минуту, тогда пропускная способность системы составляет 99%, и сборщик пропускной способности может принимать более длительные паузы.
В настоящее время системы крупных интернет-компаний в основном используют низкую задержку, чтобы избежать потери пользовательского опыта, вызванной длительной паузой GC.Показатели измерения необходимо сочетать с SLA службы приложений, в основном по следующим двум пунктам:
Короче говоря, этоВремя паузы не превышает TP9999 службы приложений, а пропускная способность GC не менее 99,99%. Например, если предположить, что TP9999 службы A составляет 80 мс, а средняя пауза GC составляет 30 мс, то максимальное время паузы службы предпочтительно не должно превышать 80 мс, а частота GC должна контролироваться на уровне более 5 минут. . Если это не может быть удовлетворено, требуется настройка или параллельное резервирование за счет дополнительных ресурсов. (Вы можете остановиться и посмотреть на индикатор минутного уровня gc.meantime на платформе мониторинга. Если он превышает 6 мс, пропускная способность одномашинного GC не достигнет 4 9 с.)
Примечания: В дополнение к этим двум показателям существуют также такие показатели, как след (измерение размера ресурса) и скорость отклика.Системы реального времени, такие как Интернет, стремятся к низкой задержке, в то время как многие встроенные системы преследуют след.
3.1.2 Понимание причины GC
Получив журнал GC, мы можем просто проанализировать ситуацию GC. С помощью некоторых инструментов мы можем более интуитивно увидеть распределение Cause. На следующем рисунке показан график, нарисованный gceasy:
Как показано на рисунке выше, мы можем четко знать, что вызвало GC и время, затраченное каждый раз, но для анализа проблемы GC мы должны сначала понять причину GC, то есть, при каких условиях JVM выбирает выполнение. Операции GC, обратитесь к исходному коду точки доступа для конкретной классификации причин: src/share/vm/gc/shared/gcCause.hpp и src/share/vm/gc/shared/gcCause.cpp.
const char* GCCause::to_string(GCCause::Cause cause) {
switch (cause) {
case _java_lang_system_gc:
return "System.gc()";
case _full_gc_alot:
return "FullGCAlot";
case _scavenge_alot:
return "ScavengeAlot";
case _allocation_profiler:
return "Allocation Profiler";
case _jvmti_force_gc:
return "JvmtiEnv ForceGarbageCollection";
case _gc_locker:
return "GCLocker Initiated GC";
case _heap_inspection:
return "Heap Inspection Initiated GC";
case _heap_dump:
return "Heap Dump Initiated GC";
case _wb_young_gc:
return "WhiteBox Initiated Young GC";
case _wb_conc_mark:
return "WhiteBox Initiated Concurrent Mark";
case _wb_full_gc:
return "WhiteBox Initiated Full GC";
case _no_gc:
return "No GC";
case _allocation_failure:
return "Allocation Failure";
case _tenured_generation_full:
return "Tenured Generation Full";
case _metadata_GC_threshold:
return "Metadata GC Threshold";
case _metadata_GC_clear_soft_refs:
return "Metadata GC Clear Soft References";
case _cms_generation_full:
return "CMS Generation Full";
case _cms_initial_mark:
return "CMS Initial Mark";
case _cms_final_remark:
return "CMS Final Remark";
case _cms_concurrent_mark:
return "CMS Concurrent Mark";
case _old_generation_expanded_on_last_scavenge:
return "Old Generation Expanded On Last Scavenge";
case _old_generation_too_full_to_scavenge:
return "Old Generation Too Full To Scavenge";
case _adaptive_size_policy:
return "Ergonomics";
case _g1_inc_collection_pause:
return "G1 Evacuation Pause";
case _g1_humongous_allocation:
return "G1 Humongous Allocation";
case _dcmd_gc_run:
return "Diagnostic Command";
case _last_gc_cause:
return "ILLEGAL VALUE - last gc cause - ILLEGAL VALUE";
default:
return "unknown GCCause";
}
ShouldNotReachHere();
}
Несколько причин GC, на которых следует сосредоточиться:
-
Система.gc():Запустите операцию GC вручную.
-
CMS:Некоторые действия во время выполнения CMS GC с упором на два этапа STW: начальную оценку CMS и итоговую оценку CMS.
-
Ошибка продвижения:В области «Старый» недостаточно места для размещения объектов, продвигаемых в области «Молодой» (даже если общая доступная память достаточно велика).
-
Ошибка параллельного режима:Во время работы CMS GC места, зарезервированного в Старой области, недостаточно для размещения новых объектов, в это время коллектор выродится, что серьезно повлияет на производительность GC.Следующий случай - такой сценарий.
-
GCLocker инициировал GC:Если поток выполняется в критической секции JNI и ему просто нужно выполнить сборку мусора, GC Locker предотвратит выполнение сборки мусора и не позволит другим потокам войти в критическую секцию JNI до тех пор, пока последний поток не выйдет из критической секции и не запустит сборщик мусора.
Когда использовать эти причины для запуска перезапуска, вы можете посмотреть код CMS, который будет обсуждаться не здесь, а в /src/hotspot/share/gc/cms/concurrentMarkSweepGeneration.cpp.
bool CMSCollector::shouldConcurrentCollect() {
LogTarget(Trace, gc) log;
if (_full_gc_requested) {
log.print("CMSCollector: collect because of explicit gc request (or GCLocker)");
return true;
}
FreelistLocker x(this);
// ------------------------------------------------------------------
// Print out lots of information which affects the initiation of
// a collection.
if (log.is_enabled() && stats().valid()) {
log.print("CMSCollector shouldConcurrentCollect: ");
LogStream out(log);
stats().print_on(&out);
log.print("time_until_cms_gen_full %3.7f", stats().time_until_cms_gen_full());
log.print("free=" SIZE_FORMAT, _cmsGen->free());
log.print("contiguous_available=" SIZE_FORMAT, _cmsGen->contiguous_available());
log.print("promotion_rate=%g", stats().promotion_rate());
log.print("cms_allocation_rate=%g", stats().cms_allocation_rate());
log.print("occupancy=%3.7f", _cmsGen->occupancy());
log.print("initiatingOccupancy=%3.7f", _cmsGen->initiating_occupancy());
log.print("cms_time_since_begin=%3.7f", stats().cms_time_since_begin());
log.print("cms_time_since_end=%3.7f", stats().cms_time_since_end());
log.print("metadata initialized %d", MetaspaceGC::should_concurrent_collect());
}
// ------------------------------------------------------------------
// If the estimated time to complete a cms collection (cms_duration())
// is less than the estimated time remaining until the cms generation
// is full, start a collection.
if (!UseCMSInitiatingOccupancyOnly) {
if (stats().valid()) {
if (stats().time_until_cms_start() == 0.0) {
return true;
}
} else {
if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
log.print(" CMSCollector: collect for bootstrapping statistics: occupancy = %f, boot occupancy = %f",
_cmsGen->occupancy(), _bootstrap_occupancy);
return true;
}
}
}
if (_cmsGen->should_concurrent_collect()) {
log.print("CMS old gen initiated");
return true;
}
CMSHeap* heap = CMSHeap::heap();
if (heap->incremental_collection_will_fail(true /* consult_young */)) {
log.print("CMSCollector: collect because incremental collection will fail ");
return true;
}
if (MetaspaceGC::should_concurrent_collect()) {
log.print("CMSCollector: collect for metadata allocation ");
return true;
}
// CMSTriggerInterval starts a CMS cycle if enough time has passed.
if (CMSTriggerInterval >= 0) {
if (CMSTriggerInterval == 0) {
// Trigger always
return true;
}
// Check the CMS time since begin (we do not check the stats validity
// as we want to be able to trigger the first CMS cycle as well)
if (stats().cms_time_since_begin() >= (CMSTriggerInterval / ((double) MILLIUNITS))) {
if (stats().valid()) {
log.print("CMSCollector: collect because of trigger interval (time since last begin %3.7f secs)",
stats().cms_time_since_begin());
} else {
log.print("CMSCollector: collect because of trigger interval (first collection)");
}
return true;
}
}
return false;
}
3.2 Является ли это проблемой, вызванной сборщиком мусора?
Является ли это результатом (явлением) или причиной, в процессе борьбы с проблемой GC, как судить, является ли это сбоем, вызванным GC, или сама система вызвала проблему GC. Здесь мы продолжаем рассматривать случай, упомянутый в начале этой статьи: "Время GC увеличивается, количество блоков потоков увеличивается, медленные запросы увеличиваются, а загрузка процессора высока. Как определить, что является первопричиной?" Автор здесь основывается на собственный опыт. Для справки были составлены четыре метода суждения:
-
Анализ времени:Событие, которое происходит первым, скорее всего, является первопричиной.Используйте методы мониторинга для анализа аномальных моментов времени каждого индикатора и восстановления временной шкалы события.Если сначала наблюдается загрузка ЦП (должен быть достаточный временной интервал), вся проблема влияет на цепочку.Это может быть: высокая загрузка процессора -> увеличение медленных запросов -> увеличение времени GC -> увеличение блоков потоков -> увеличение RT.
-
Вероятностный анализ:Используйте статистическую вероятность, чтобы сделать выводы, основанные на опыте исторических проблем, и проанализируйте их по типу от близкого к далекому.Если в прошлом было много проблем с медленным поиском, то вся цепочка влияния проблемы может быть: медленное увеличение запроса -> Потребление времени GC увеличивается -> Высокая загрузка ЦП -> Увеличение блока потока -> Увеличение RT.
-
анализ эксперимента:Смоделируйте проблемный участок с помощью отработки неисправностей и других методов, активируйте некоторые условия (одно или несколько) и наблюдайте, возникнет ли проблема. может быть: увеличение блоков потоков -> высокая загрузка ЦП -> увеличение медленных запросов -> увеличение потребления времени GC -> увеличение RT.
-
Анализ контрдоказательств:Провести встречный анализ на одном из проявлений, то есть определить, связано ли появление появления с результатом.Например, мы наблюдали с точки зрения всего кластера, что медленная проверка и ЦП некоторых узлы в норме, но есть и проблема, то весь кластер.Цепочка воздействия проблемы может быть: увеличивается время GC -> увеличивается количество блоков потока -> увеличивается RT.
Для разных первопричин последующие методы анализа совершенно разные. Если загрузка процессора высока, вам может понадобиться использовать график пламени, чтобы увидеть горячие точки.Если медленный запрос увеличивается, вам может потребоваться взглянуть на ситуацию с БД.Если это вызвано блокировкой потока, вам может потребоваться посмотрите на ситуацию с конкуренцией замков.Наконец, если все доказывает, что нет Если есть проблема, то может быть проблема с GC, и вы можете продолжить анализ проблемы GC.
3.3 Введение в классификацию задач
3.3.1 Типы мутаторов
Типы мутаторов в основном делятся на два типа в соответствии с диаграммой соотношения времени выживания объекта. Аналогичные утверждения также упоминаются в гипотезе слабого поколения. Как показано на рисунке ниже, «Время выживания» представляет время выживания объекта, а «Скорость " представляет коэффициент размещения объектов. :
-
ИО интерактивный:Большинство современных сервисов в Интернете относятся к этому типу, такие как распределенные службы RPC, MQ, шлюзы HTTP и т. д., которые не требуют много памяти.Большинство объектов умрут в течение времени TP9999.Чем больше область Юнга, лучшее.
-
Тип расчета МЭМ:В основном распределенные вычисления данных Hadoop, распределенное хранилище HBase, Cassandra, самостоятельный распределенный кэш и т. д. имеют высокие требования к памяти, длительное время существования объекта, и чем больше старая область, тем лучше.
Конечно, помимо двух, есть и промежуточные сценарии, в этой статье в основном обсуждается первый случай. Карта распределения времени выживания объекта имеет для нас очень важное руководящее значение для установки параметров GC.Следующий рисунок может просто рассчитать границу поколений.
3.3.2 Классификация проблем GC
Автор выбрал девять различных типов задач GC, охватывающих большинство сценариев.Если есть сценарии получше, укажите их в области комментариев.
-
Неожиданный сборщик мусора:Случайный GC на самом деле не обязательно должен происходить, мы можем избежать его каким-то образом.
- Космический шок:О проблеме пространственных колебаний см. «Сценарий 1: Пространственные колебания, вызванные динамическим расширением».
- Явный GC:Чтобы показать проблему выполнения GC, см. «Сценарий 2: явный GC Go and Stay».
-
Частичный GC:GC для операций частичного сбора собираются только определенные поколения/разделы.
-
Молодой ГК:Действие по сбору молодых участков в поколенческом сборе также можно назвать Minor GC.
- ПарНовый:Часто встречается молодой GC, см. «Сценарий 4: Преждевременное повышение».
-
Старый ГК:Действие по сбору области Old в поколенческой коллекции также можно назвать Major GC, а некоторые еще называют Full GC, но на самом деле это название не стандартизировано. Параметр находится только в параметре Инициировать молодой GC перед примечанием.
- CMS:Старый GC встречается часто, см. «Сценарий 5: CMS Old GC часто».
- CMS:Старый GC выполняется нечасто, но занимает много времени См. «Сценарий 6: Одиночная CMS Старый GC занимает много времени».
-
-
Полный ГК:Полная сборка GC восстанавливает всю кучу, и время STW будет относительно большим. Как только это произойдет, воздействие будет сильнее, и его также можно назвать основным GC. См. «Сценарий 7: Фрагментация памяти и деградация сборщика».
-
Метапространство:Освобождение метапространства вызывает проблемы, см. «Сценарий 3: MetaSpace OOM».
-
Прямая память:Прямая высвобождение памяти (также называемой памятью вне кучи) вызывает проблемы, см. «Сценарий 8: OOM памяти вне кучи».
-
JNI:Проблемы, вызванные собственными методами, см. в разделе «Сценарий 9: проблемы с сборщиком мусора, вызванные JNI».
3.3.3 Устранение неполадок
проблемаСложность решения обратно пропорциональна распространенности, большинство из нас может найти похожие проблемы с помощью различных поисковых систем, а затем попытаться решить их теми же средствами. Когда проблема не может найти похожие проблемы на разных веб-сайтах, может быть две ситуации, одна не является проблемой, а другая - столкнуться с глубоко скрытой проблемой, для отладки может потребоваться углубиться в уровень исходного кода. Для следующих сценариев проблем с сборщиком мусора сложность устранения неполадок увеличивается сверху вниз.
4. Общий анализ сценариев и решение
4.1 Сценарий 1: Пространственный удар, вызванный динамическим расширением
4.1.1 Феномен
СлужитьКоличество сборщиков мусора велико, когда он только запущен, остается много максимального места, но GC все еще происходит, В этом случае мы можем наблюдать за журналом GC или отслеживать изменение места в куче с помощью инструментов мониторинга. Обычно причиной сборки мусора является сбой распределения, и в журнале сборки мусора будет отмечено, что после сборки мусора размер каждого пространства в куче будет скорректирован, как показано на следующем рисунке:
4.1.2 Причины
в параметрах JVM-Xms
и-Xmx
Если настройки несовместимы, для хранения информации во время инициализации используется только начальный размер пространства -Xms.Если места недостаточно, оно будет применено к операционной системе.В этом случае необходимо выполнить GC. Конкретно черезConcurrentMarkSweepGeneration::compute_new_size()
Метод вычисляет новый размер пространства:
void ConcurrentMarkSweepGeneration::compute_new_size() {
assert_locked_or_safepoint(Heap_lock);
// If incremental collection failed, we just want to expand
// to the limit.
if (incremental_collection_failed()) {
clear_incremental_collection_failed();
grow_to_reserved();
return;
}
// The heap has been compacted but not reset yet.
// Any metric such as free() or used() will be incorrect.
CardGeneration::compute_new_size();
// Reset again after a possible resizing
if (did_compact()) {
cmsSpace()->reset_after_compaction();
}
}
Кроме того, если осталось много места, будет выполнена операция сжатия, и JVM пройдет-XX:MinHeapFreeRatio
и-XX:MaxHeapFreeRatio
Чтобы контролировать соотношение расширения и сжатия, регулировка этих двух значений также может контролировать время расширения.Например, расширение заключается в использованииGenCollectedHeap::expand_heap_and_allocate()
Для завершения код выглядит следующим образом:
HeapWord* GenCollectedHeap::expand_heap_and_allocate(size_t size, bool is_tlab) {
HeapWord* result = NULL;
if (_old_gen->should_allocate(size, is_tlab)) {
result = _old_gen->expand_and_allocate(size, is_tlab);
}
if (result == NULL) {
if (_young_gen->should_allocate(size, is_tlab)) {
result = _young_gen->expand_and_allocate(size, is_tlab);
}
}
assert(result == NULL || is_in_reserved(result), "result not in heap");
return result;
}
Всю модель масштабирования можно понять, взглянув на этот рисунок.Когда выделенное пространство превышает размер уровня низкой/высокой воды, емкость также будет соответствующим образом скорректирована:
4.1.3 Стратегия
должность: наблюдайте, является ли зафиксированное соотношение области Old/MetaSpace во время запуска CMS GC фиксированным значением, или наблюдайте за общим использованием памяти, как указано выше.
решать: насколько это возможноУстановите фиксированный параметр конфигурации размера парного пространства.,как-Xms
и-Xmx
,-XX:MaxNewSize
и-XX:NewSize
,-XX:MetaSpaceSize
и-XX:MaxMetaSpaceSize
Ждать.
4.1.4 Резюме
Вообще говоря, нам нужно убедиться, что куча виртуальной машины Java стабильна, гарантируя, что-Xms
и-Xmx
Устанавливается значение (то есть начальное значение совпадает с максимальным значением), и получается стабильная куча Аналогично, аналогичные проблемы есть и в области MetaSpace. Однако также полезно колебаться в пространстве, не гонясь за временем простоя, и его можно динамически масштабировать для экономии места, например, в многофункциональном клиентском приложении Java.
Хоть эта проблема и элементарная, но вероятность возникновения действительно не маленькая, особенно когда какие-то нормы не очень здравые.
4.2 Сценарий 2. Явный сборщик мусора уходит и остается
4.2.1 Феномен
В дополнение к CMS GC, инициируемому расширением и сокращением емкости, существует несколько инициирующих условий, таких как достижение порога высвобождения в старой области, недостаточное пространство в MetaSpace, сбой продвижения молодой области и сбой гарантии большого объекта.Если ни одна из этих ситуаций не возникает, ГК срабатывает? В этом случае метод System.gc может быть вызван вручную в коде.В это время вы можете найти причину GC в журнале GC для подтверждения. Так есть ли проблема с таким GC?Глядя на некоторую информацию в Интернете, некоторые люди говорят, что его можно добавить-XX:+DisableExplicitGC
параметр, чтобы избежать этого GC, и некоторые люди говорят, что этот параметр нельзя добавить, и добавление этого параметра повлияет на восстановление Native Memory. Давайте сначала поговорим о заключении: автор рекомендует сохранить System.gc, так зачем его оставлять? Давайте проанализируем это вместе.
4.2.2 Причины
Найдите исходный код System.gc в Hotspot, вы можете найти увеличение-XX:+DisableExplicitGC
После параметра этот метод становится пустым методом, если его не добавить, то он будет вызыватьсяUniverse::heap()::collect
метод, продолжайте следовать этому методу и обнаружите, что System.gc инициирует полный сборщик мусора STW для сбора всей кучи.
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
void GenCollectedHeap::collect(GCCause::Cause cause) {
if (cause == GCCause::_wb_young_gc) {
// Young collection for the WhiteBox API.
collect(cause, YoungGen);
} else {
#ifdef ASSERT
if (cause == GCCause::_scavenge_alot) {
// Young collection only.
collect(cause, YoungGen);
} else {
// Stop-the-world full collection.
collect(cause, OldGen);
}
#else
// Stop-the-world full collection.
collect(cause, OldGen);
#endif
}
}
Сохранить System.gc
Вот дополнительный пункт знаний,CMS GC разделен на два режима: фоновый и передний план., первый является параллельным сбором в нашем обычном понимании, который не влияет на нормальную работу бизнес-потока, но Foreground Collector сильно отличается, он будет выполнять компактный GC. Этот компактный GC использует тот же алгоритм Lisp2, что и Serial Old GC.Он использует Mark-Compact для выполнения полного GC, обычно называемого MSC (Mark-Sweep-Compact), который собирает область Young и кучу Java, область Old и MetaSpace. Из приведенной выше главы об алгоритмах мы знаем, что стоимость компакта огромна, поэтому при использовании Foreground Collector это приведет к очень долгому STW. Очень опасно, если System.gc часто вызывается в приложении.
удалить System.gc
Если его отключить, это вызовет еще одну проблему с утечкой памяти.На этот раз нам нужно поговорить о DirectByteBuffer, который имеет характеристики нулевого копирования и используется различными фреймворками NIO, такими как Netty, и будет использовать память вне кучи. Память кучи управляется самой JVM, а память вне кучи должна быть освобождена вручную.DirectByteBuffer не имеет финализатора, а его очистка Native Memory выполняется черезsun.misc.Cleaner
Autocomplete — это инструмент очистки, основанный на PhantomReference, который легче, чем обычный Finalizer.
В процессе выделения места для DirectByteBuffer System.gc будет вызываться явно, надеясь заставить бесполезные объекты DirectByteBuffer освободить связанную с ними Native Memory посредством полного GC. Ниже приведена реализация кода:
// These methods should be called whenever direct memory is allocated or
// freed. They allow the user to control the amount of direct memory
// which a process may access. All sizes are specified in bytes.
static void reserveMemory(long size) {
synchronized (Bits.class) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
if (size <= maxMemory - reservedMemory) {
reservedMemory += size;
return;
}
}
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
if (reservedMemory + size > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
}
}
HotSpot VM будет выполнять обработку ссылок только для объектов в старом во время старой сборки мусора и будет выполнять обработку ссылок только для объектов в молодом во время молодой сборки мусора. Объекты DirectByteBuffer в Young будут обрабатываться во время Young GC, то есть, если CMS GC выполняется, эталонная обработка будет выполняться на Old, что, в свою очередь, может инициировать Cleaner для очистки мертвых объектов DirectByteBuffer. Однако, если вы давно не делали GC или делали только Young GC, Cleaner не сработает в Old, тогда Native Memory, связанная с DirectByteBuffer, который уже умер, но был повышен до Old, может быть недоступен , Своевременный выпуск. Эти функции реализации позволяют полагаться на System.gc для запуска сборки мусора, чтобы гарантировать своевременную очистку DirectByteMemory. если открыто-XX:+DisableExplicitGC
, работа по очистке может быть не завершена вовремя, поэтому будет OOM прямой памяти.
4.2.3 Стратегия
Из приведенного выше анализа видно, что существуют определенные точки риска, независимо от того, зарезервировано оно или удалено, но в настоящее время RPC-связь в Интернете будет часто использовать NIO, поэтому автор рекомендует оставить его здесь. Кроме того, JVM также предоставляет-XX:+ExplicitGCInvokesConcurrent
и-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
Параметры для изменения типа триггера System.gc с переднего плана на фоновый, а фон также будет выполнять обработку ссылок, так что накладные расходы STW могут быть значительно уменьшены, а NIO Direct Memory OOM не произойдет.
4.2.4 Резюме
Больше, чем CMS, откройте в G1 или ZGCExplicitGCInvokesConcurrent
Mode будет использовать высокопроизводительный параллельный метод сбора для сбора, но по-прежнему рекомендуется хорошо поработать в спецификации кода, стандартизировать использование System.gc.
PS HotSpot имеет специальную обработку для System.gc.Главное заключается в том, будет ли System.gc запускать обновление статистики/пороговых данных GC, как обычный GC.Многие алгоритмы GC в HotSpot имеют адаптивные функции.Параметры, используемые в следующем GC будет определяться в соответствии с эффективностью предыдущей коллекции, но System.gc не обновляет эту статистику по умолчанию, чтобы избежать вмешательства пользовательского принудительного GC в эти адаптивные функции (см. параметр -XX:+UseAdaptiveSizePolicyWithSystemGC, значение по умолчанию равно ложный).
4.3 Сценарий 3: OOM в области метапространства
4.3.1 Феномен
JVM запускается после запуска или в какой-то момент,Используемый размер MetaSpace продолжает расти, и его нельзя освобождать каждый раз при выполнении GC.Увеличение пространства MetaSpace не может полностью решить проблему..
4.3.2 Причины
Прежде чем обсуждать, почему OOM, давайте посмотрим, какие данные будут храниться в этой области.До Java7 пул строковых констант размещался в области Perm, и все intern Strings будут храниться здесь, потому что String.intern не контролируется, так-XX:MaxPermSize
Значение не очень хорошо ставить, часто появляютсяjava.lang.OutOfMemoryError: PermGen space
Исключения, поэтому после Java7 несколько элементов, таких как константные литералы пула (Literal), статические переменные класса (Class Static) и символические ссылки (Symbols Reference), были перемещены в кучу. После Java8 PermGen также был удален и заменен MetaSpace.
На нижнем уровне JVM обращается за картой памяти к операционной системе через интерфейс mmap.Каждый раз, когда она обращается за 2 МБ пространства, вот карта виртуальной памяти. На самом деле она не потребляет 2 МБ основной памяти, а потребляет только память, когда она используется позже. Запрошенная память помещается в связанный список VirtualSpaceList как один из узлов.
На верхнем уровне MetaSpace в основном состоит из Klass Metaspace и NoKlass Metaspace.
-
Класс МетаСпейс:Он используется для хранения Klass, который является структурой данных времени выполнения файла класса в JVM.Эта часть по умолчанию помещается в сжатое пространство указателя класса, которое представляет собой непрерывную область памяти, за которой следует куча. Сжатое пространство указателя класса не требуется, если установлено
-XX:-UseCompressedClassPointers
,или-Xmx
Если параметр больше 32 G, такой памяти не будет, в этом случае Klass будет храниться в метапространстве NoKlass. - НоКласс Метаспейс:Он специально используется для хранения другого контента, связанного с Klass, такого как Method, ConstantPool и т. д., который может состоять из нескольких дискретных блоков памяти. Хотя он называется NoKlass Metaspace, на самом деле он может хранить контент Klass, соответствующие сценарии были упомянуты выше.
Конкретные определения можно найти в исходном коде shared/vm/memory/metaspace.hpp:
class Metaspace : public AllStatic {
friend class MetaspaceShared;
public:
enum MetadataType {
ClassType,
NonClassType,
MetadataTypeCount
};
enum MetaspaceType {
ZeroMetaspaceType = 0,
StandardMetaspaceType = ZeroMetaspaceType,
BootMetaspaceType = StandardMetaspaceType + 1,
AnonymousMetaspaceType = BootMetaspaceType + 1,
ReflectionMetaspaceType = AnonymousMetaspaceType + 1,
MetaspaceTypeCount
};
private:
// Align up the word size to the allocation word size
static size_t align_word_size_up(size_t);
// Aligned size of the metaspace.
static size_t _compressed_class_space_size;
static size_t compressed_class_space_size() {
return _compressed_class_space_size;
}
static void set_compressed_class_space_size(size_t size) {
_compressed_class_space_size = size;
}
static size_t _first_chunk_word_size;
static size_t _first_class_chunk_word_size;
static size_t _commit_alignment;
static size_t _reserve_alignment;
DEBUG_ONLY(static bool _frozen;)
// Virtual Space lists for both classes and other metadata
static metaspace::VirtualSpaceList* _space_list;
static metaspace::VirtualSpaceList* _class_space_list;
static metaspace::ChunkManager* _chunk_manager_metadata;
static metaspace::ChunkManager* _chunk_manager_class;
static const MetaspaceTracer* _tracer;
}
Почему нельзя освободить объект MetaSpace? Давайте рассмотрим следующие два момента:
-
Управление памятью MetaSpace:Жизненный цикл класса и его метаданных такой же, как и у соответствующего загрузчика класса.Пока жив загрузчик класса, метаданные класса в метапространстве также живы и не могут быть переработаны. Каждый загрузчик имеет отдельное пространство для хранения, а указатели SpaceManager* управляются ClassLoaderMetaspace, которые изолированы друг от друга.
-
Эластичное масштабирование MetaSpace:Так как пространство MetaSpace и куча не вместе, это пространство можно установить без настройки или отдельно.Как правило, устанавливается MaxMetaSpaceSize, чтобы избежать исчерпания памяти виртуальной машины MetaSpace.Во время выполнения процесса, если фактический размер меньше этого значения, JVM пройдет через
-XX:MinMetaspaceFreeRatio
и-XX:MaxMetaspaceFreeRatio
Два параметра динамически управляют размером всего MetaSpace.Подробнее см.MetaSpaceGC::compute_new_size()
метод (код ниже), этот метод будет вызываться, когда несколько сборщиков, таких как CMSColector и G1CollectorHeap, выполняют сборку мусора. Это будет основано наused_after_gc
,MinMetaspaceFreeRatio
иMaxMetaspaceFreeRatio
Эти три значения вычисляют новый_capacity_until_GC
значение (водяной знак). Тогда по факту_capacity_until_GC
использование значенияMetaspaceGC::inc_capacity_until_GC()
иMetaspaceGC::dec_capacity_until_GC()
Чтобы расширить или сжать, этот процесс также можно понять со ссылкой на модель расширения в Сценарии 1.
void MetaspaceGC::compute_new_size() {
assert(_shrink_factor <= 100, "invalid shrink factor");
uint current_shrink_factor = _shrink_factor;
_shrink_factor = 0;
const size_t used_after_gc = MetaspaceUtils::committed_bytes();
const size_t capacity_until_GC = MetaspaceGC::capacity_until_GC();
const double minimum_free_percentage = MinMetaspaceFreeRatio / 100.0;
const double maximum_used_percentage = 1.0 - minimum_free_percentage;
const double min_tmp = used_after_gc / maximum_used_percentage;
size_t minimum_desired_capacity =
(size_t)MIN2(min_tmp, double(max_uintx));
// Don't shrink less than the initial generation size
minimum_desired_capacity = MAX2(minimum_desired_capacity,
MetaspaceSize);
log_trace(gc, metaspace)("MetaspaceGC::compute_new_size: ");
log_trace(gc, metaspace)(" minimum_free_percentage: %6.2f maximum_used_percentage: %6.2f",
minimum_free_percentage, maximum_used_percentage);
log_trace(gc, metaspace)(" used_after_gc : %6.1fKB", used_after_gc / (double) K);
size_t shrink_bytes = 0;
if (capacity_until_GC < minimum_desired_capacity) {
// If we have less capacity below the metaspace HWM, then
// increment the HWM.
size_t expand_bytes = minimum_desired_capacity - capacity_until_GC;
expand_bytes = align_up(expand_bytes, Metaspace::commit_alignment());
// Don't expand unless it's significant
if (expand_bytes >= MinMetaspaceExpansion) {
size_t new_capacity_until_GC = 0;
bool succeeded = MetaspaceGC::inc_capacity_until_GC(expand_bytes, &new_capacity_until_GC);
assert(succeeded, "Should always succesfully increment HWM when at safepoint");
Metaspace::tracer()->report_gc_threshold(capacity_until_GC,
new_capacity_until_GC,
MetaspaceGCThresholdUpdater::ComputeNewSize);
log_trace(gc, metaspace)(" expanding: minimum_desired_capacity: %6.1fKB expand_bytes: %6.1fKB MinMetaspaceExpansion: %6.1fKB new metaspace HWM: %6.1fKB",
minimum_desired_capacity / (double) K,
expand_bytes / (double) K,
MinMetaspaceExpansion / (double) K,
new_capacity_until_GC / (double) K);
}
return;
}
// No expansion, now see if we want to shrink
// We would never want to shrink more than this
assert(capacity_until_GC >= minimum_desired_capacity,
SIZE_FORMAT " >= " SIZE_FORMAT,
capacity_until_GC, minimum_desired_capacity);
size_t max_shrink_bytes = capacity_until_GC - minimum_desired_capacity;
// Should shrinking be considered?
if (MaxMetaspaceFreeRatio < 100) {
const double maximum_free_percentage = MaxMetaspaceFreeRatio / 100.0;
const double minimum_used_percentage = 1.0 - maximum_free_percentage;
const double max_tmp = used_after_gc / minimum_used_percentage;
size_t maximum_desired_capacity = (size_t)MIN2(max_tmp, double(max_uintx));
maximum_desired_capacity = MAX2(maximum_desired_capacity,
MetaspaceSize);
log_trace(gc, metaspace)(" maximum_free_percentage: %6.2f minimum_used_percentage: %6.2f",
maximum_free_percentage, minimum_used_percentage);
log_trace(gc, metaspace)(" minimum_desired_capacity: %6.1fKB maximum_desired_capacity: %6.1fKB",
minimum_desired_capacity / (double) K, maximum_desired_capacity / (double) K);
assert(minimum_desired_capacity <= maximum_desired_capacity,
"sanity check");
if (capacity_until_GC > maximum_desired_capacity) {
// Capacity too large, compute shrinking size
shrink_bytes = capacity_until_GC - maximum_desired_capacity;
shrink_bytes = shrink_bytes / 100 * current_shrink_factor;
shrink_bytes = align_down(shrink_bytes, Metaspace::commit_alignment());
assert(shrink_bytes <= max_shrink_bytes,
"invalid shrink size " SIZE_FORMAT " not <= " SIZE_FORMAT,
shrink_bytes, max_shrink_bytes);
if (current_shrink_factor == 0) {
_shrink_factor = 10;
} else {
_shrink_factor = MIN2(current_shrink_factor * 4, (uint) 100);
}
log_trace(gc, metaspace)(" shrinking: initThreshold: %.1fK maximum_desired_capacity: %.1fK",
MetaspaceSize / (double) K, maximum_desired_capacity / (double) K);
log_trace(gc, metaspace)(" shrink_bytes: %.1fK current_shrink_factor: %d new shrink factor: %d MinMetaspaceExpansion: %.1fK",
shrink_bytes / (double) K, current_shrink_factor, _shrink_factor, MinMetaspaceExpansion / (double) K);
}
}
// Don't shrink unless it's significant
if (shrink_bytes >= MinMetaspaceExpansion &&
((capacity_until_GC - shrink_bytes) >= MetaspaceSize)) {
size_t new_capacity_until_GC = MetaspaceGC::dec_capacity_until_GC(shrink_bytes);
Metaspace::tracer()->report_gc_threshold(capacity_until_GC,
new_capacity_until_GC,
MetaspaceGCThresholdUpdater::ComputeNewSize);
}
}
Как видно из сценария 1, чтобы избежать дополнительного потребления GC, вызванного эластичным масштабированием, мы-XX:MetaSpaceSize
и-XX:MaxMetaSpaceSize
Два значения установлены как фиксированные, но это также приведет к невозможности расширения, когда места недостаточно, а затем часто вызывает GC, в конечном итоге OOM. Таким образом, основная причина заключается в том, что ClassLoader продолжает загружать новые классы в память, что обычно происходит при динамической загрузке классов и других ситуациях.
4.3.3 Стратегия
Поняв причину, найти и решить ее очень просто.После сброса снимка вы можете наблюдать за гистограммой (гистограммой) классов через JProfiler или MAT, или вы можете найти ее непосредственно через команду.Посмотрите на конкретный пакет под которым класс увеличился больше, и вы можете найти его. Однако иногда необходимо объединить несколько индикаторов, таких как InstBytes, KlassBytes, Bytecodes, MethodAll и так далее. Как показано на рисунке ниже, автор использовал jcmd для устранения неполадок с Orika.
jcmd <PID> GC.class_stats|awk '{print$13}'|sed 's/\(.*\)\.\(.*\)/\1/g'|sort |uniq -c|sort -nrk1
Если позиционирование невозможно с целостной точки зрения, вы можете добавить-XX:+TraceClassLoading
и-XX:+TraceClassUnLoading
Параметр содержит подробную информацию о загрузке и выгрузке класса.
4.3.4 Резюме
Принцип сложен для понимания, но найти и решить проблему относительно просто.Несколько моментов, которые часто вызывают проблемы, включают в себя classMap Orika, ASMSSerializer JSON, динамически загружаемые классы Groovy и т. д., которые в основном сосредоточены на отражении, улучшении байт-кода Javasisit. , CGLIB О технических аспектах динамических прокси, пользовательских загрузчиков классов OSGi и т. д. Другой - вовремя добавить мониторинг коэффициента использования области MetaSpace, если показатель колеблется, найти и решить проблему заранее.
4.4 Сценарий 4: Преждевременное повышение*
4.4.1 Феномен
Этот сценарий в основном встречается у коллекционеров поколений, и профессиональный термин называется «преждевременное продвижение». 90% объектов умирают в мгновение ока.Только после того, как Молодая область претерпит несколько крещений GC, она будет повышена до Старой области.Возраст GC объекта будет увеличиваться на 1 каждый раз, когда он проходит GC, и максимальный проход-XX:MaxTenuringThreshold
контролировать.
Преждевременное продвижение обычно не влияет напрямую на сборщик мусора и всегда сопровождается такими проблемами, как плавающий мусор и сбой гарантии больших объектов, но эти проблемы возникают не сразу.Мы можем наблюдать следующие явления, чтобы определить, произошло ли преждевременное продвижение.
Скорость распределения близка к скорости продвижения, объект продвигается в более молодом возрасте.
"Желаемый размер оставшегося в живых 107347968 байт,new threshold 1(max 6)” и другую информацию, указывающую на то, что после GC в это время он будет помещен в Старую область.
Полный GC чаще, а после GC старая областьКоэффициент изменения очень велик.
Например, порог восстановления, срабатывающий в Старой области, составляет 80%, а после GC он падает до 10%, а это значит, что 70% объектов в Старой области имеют очень короткое время выживания, как показано на рисунке ниже. , размер области Old меняется после каждого GC.От 2.1G до 300M, то есть перерабатывается 1.8G мусора, всего300 млн активных объектов. Вся куча в настоящее время составляет 4G, а на активные объекты приходится менее одной десятой.
Опасности преждевременного продвижения:
- Молодые сборщики мусора являются частыми, и общая пропускная способность падает.
- Полный сборщик мусора выполняется часто и может иметь большие паузы.
4.4.2 Причины
Есть две основные причины:
-
Район Young/Eden слишком мал:Прямым следствием того, что он слишком мал, является то, что время для заполнения Эдема сокращается, а объекты, которые должны быть возвращены, участвуют в GC и продвигаются. Young GC использует алгоритм копирования. Из основ мы знаем, что копирование занимает много времени. длиннее метки, то есть Young GC.Затраты времени - это по сути время копирования (CMS сканирует Card Table или G1 сканирует Remember Set и возникает проблема).Объекты, которые не перерабатываются вовремя, увеличивают стоимость переработки, поэтому Время Молодых ГК увеличивается, и в то же время не может быть быстро освобождено пространство, а также увеличивается количество Молодых ГК.
-
Скорость распределения слишком высока:Вы можете наблюдать за скоростью распределения Mutator до и после возникновения проблемы.Если есть очевидные колебания, вы можете попытаться наблюдать за трафиком сетевой карты, журналом медленных запросов промежуточного класса хранилища и другой информацией, чтобы увидеть, загружается ли большой объем данных. в память.
В то же время невозможность GC объектов повлечет за собой еще одну проблему, вызывающую динамический расчет возраста: JVM пропускает-XX:MaxTenuringThreshold
Параметр управляет возрастом продвижения.После каждого GC возраст будет увеличиваться на единицу, а максимальный возраст можно ввести в поле Old.Максимальное значение равно 15 (поскольку JVM использует 4 бита для представления возраста объекта) . Установите фиксированное значение MaxTenuringThreshold в качестве условия акции:
-
Если MaxTenuringThreshold установлен слишком большим, объекты, которые должны быть повышены, останутся в области Survivor до тех пор, пока область Survivor не переполнится.После того, как произойдет переполнение, объекты в Eden + Survivor больше не будут обновляться до старой области в соответствии с их возрастом, поэтому механизм старения объекта будет недействителен.
-
Если MaxTenuringThreshold установлено слишком мало, преждевременное продвижение означает, что объекты не могут быть полностью возвращены в область Young, большое количество краткосрочных объектов перемещается в область Old, пространство области Old быстро увеличивается, вызывая частые крупные GC, и рекультивация поколений теряет смысл и оказывает серьезное влияние на производительность GC.
Производительность одного и того же приложения в разное время различна.Выполнение специальных задач или изменение компонентов трафика вызовет колебания в распределении жизненного цикла объектов.Фиксированная настройка порога вызовет те же проблемы, что и выше, поскольку не может динамически адаптироваться Таким образом, Hotspot Порог продвижения будет скорректирован с помощью динамического расчета.
Для конкретных динамических вычислений вы можете посмотреть исходный код Hotspot, в частности, в /src/hotspot/share/gc/shared/ageTable.cpp.compute_tenuring_threshold
В методе:
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//TargetSurvivorRatio默认50,意思是:在回收之后希望survivor区的占用率达到这个比例
size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
assert(sizes[0] == 0, "no objects with age zero should be recorded");
while (age < table_size) {//table_size=16
total += sizes[age];
//如果加上这个年龄的所有对象的大小之后,占用量>期望的大小,就设置age为新的晋升阈值
if (total > desired_survivor_size) break;
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
if (PrintTenuringDistribution || UsePerfData) {
//打印期望的survivor的大小以及新计算出来的阈值,和设置的最大阈值
if (PrintTenuringDistribution) {
gclog_or_tty->cr();
gclog_or_tty->print_cr("Desired survivor size " SIZE_FORMAT " bytes, new threshold %u (max %u)",
desired_survivor_size*oopSize, result, (int) MaxTenuringThreshold);
}
total = 0;
age = 1;
while (age < table_size) {
total += sizes[age];
if (sizes[age] > 0) {
if (PrintTenuringDistribution) {
gclog_or_tty->print_cr("- age %3u: " SIZE_FORMAT_W(10) " bytes, " SIZE_FORMAT_W(10) " total",
age, sizes[age]*oopSize, total*oopSize);
}
}
if (UsePerfData) {
_perf_sizes[age]->set_value(sizes[age]*oopSize);
}
age++;
}
if (UsePerfData) {
SharedHeap* sh = SharedHeap::heap();
CollectorPolicy* policy = sh->collector_policy();
GCPolicyCounters* gc_counters = policy->counters();
gc_counters->tenuring_threshold()->set_value(result);
gc_counters->desired_survivor_size()->set_value(
desired_survivor_size*oopSize);
}
}
return result;
}
Видно, что при обходе всех объектов Hotspot начинает накапливать пространство, занимаемое всеми объектами, возраст которых равен 0. Если прибавляется пространство для всех объектов, возраст которых равен n, то используется условное значение площади Survivor. (TargetSurvivorRatio / 100, значение по умолчанию TargetSurvivorRatio равно 50) Сделайте суждение. Если оно больше этого значения, завершите цикл и сравните n с MaxTenuringThreshold. Если n мало, пороговое значение равно n. Если n велико, только максимальное порог может быть установлен на MaxTenuringThreshold.После запуска динамического возраста в старую область попадает больше объектов, что приводит к пустой трате ресурсов..
4.4.3 Стратегия
После того, как мы узнаем причину проблемы, у нас есть направление для ее решения, если онаРайон Янг/Эден слишком мал, мы можем соответствующим образом увеличить область Юнга, в то время как общая память кучи останется неизменной, как ее увеличить? В общем, размер Old должен быть примерно в 2-3 раза больше, чем у активного объекта, учитывая проблему с плавающим мусором, лучше всего примерно в 3 раза, а остальное можно выделить в область Young.
Принимая во внимание типичную преждевременную оптимизацию продвижения автора, исходная конфигурация: Young 1,2G + Old 2,8G.Наблюдая за ситуацией с CMS GC, было обнаружено, что уцелевших объектов около 300–400M, поэтому старое 1,5G корректируется, а оставшиеся 2,5G точек до района Молодой. Регулируется только один параметр размера области Юнга (-Xmn
), весь сборщик мусора JVM Young был сокращен с 26 раз до 11 раз в минуту, а однократное время не увеличилось, общее время сборщика мусора было уменьшено с 1100 мс до 500 мс, а время сборщика мусора CMS также было уменьшено примерно с от 40 минут до одного раза в 7 часов 30 минут.
Если скорость распределения слишком высока:
-
Иногда больше: Найдите код проблемы с помощью инструмента анализа памяти и выполните некоторые оптимизации бизнес-логики.
-
всегда больше: Текущий сборщик больше не может соответствовать ожиданиям Мутатора, в этом случае либо расширяется ВМ Мутатора, либо корректируется тип сборщика мусора, либо увеличивается пространство.
4.4.4 Резюме
Проблема преждевременного раскрутки вообще не особо очевидна, но через длительный промежуток времени может быть волна таких проблем как деградация коллектора, так что нам все равно придется заранее ее избегать.Вы можете посмотреть есть ли эти явления в вашем system, если они совпадают. , можно попробовать оптимизировать. Окупаемость одной строки оптимизации кода по-прежнему очень высока.
Если в процессе наблюдения за изменением соотношения до и после Старой площади обнаружится, что доля, которая может быть восстановлена, очень мала, например, из 80% восстанавливается только 60%, а это значит, что большая часть наших объекты живы, и пространство Старой области можно соответствующим образом отрегулировать.
4.4.5 Закуски
Что касается того, как выбрать конкретное значение NewRatio при корректировке соотношения Young to Old, то проблема абстрагируется в модель коллектора, и находятся следующие ключевые показатели, которые можно рассчитать по своему сценарию.
-
NewRatio значения r и va, Вp, Вyc, Вoc,деньsСуществует определенная функциональная корреляция (rsЧем меньше r, тем больше r, тем меньше r vpЧем меньше, ..., я пытался использовать NN для помощи в моделировании раньше, но я не полностью рассчитал конкретную формулу, студенты, у которых есть идеи, могут дать свой ответ в области комментариев).
-
Общее время паузы T равно общему времени GC Юнга Tycи общее время старого GC Tocсумма, где Тycс vycи vpсвязанные, Тocс vocСвязанный.
-
После игнорирования времени GC временной интервал между двумя Young GC должен быть больше, чем время TP9999, чтобы объекты можно было максимально перерабатывать в районе Эдема, что может сократить множество пауз.
4.5 Сценарий 5: частые старые сборщики мусора CMS*
4.5.1 Феномен
Старая область часто делает CMS GC, но каждый раз не особо долго, и общий максимальный STW тоже в допустимых пределах, но пропускная способность сильно падает из-за частого GC.
4.5.2 Причины
Эта ситуация довольно распространена.В основном, после завершения Young GC, фоновый поток concurrentMarkSweepThread, отвечающий за обработку CMS GC, будет непрерывно опрашивать, использоватьshouldConcurrentCollect()
Метод выполняет проверку, чтобы определить, соблюдаются ли условия рециркуляции. Если условие выполнено, используйтеcollect_in_background()
Запустите сборщик мусора в фоновом режиме. Решение опроса состоит в том, чтобы использоватьsleepBeforeNextCycle()
метод, период интервала-XX:CMSWaitDuration
решение, по умолчанию 2 с.
Конкретный код находится в: src/hotspot/share/gc/cms/concurrentMarkSweepThread.cpp.
void ConcurrentMarkSweepThread::run_service() {
assert(this == cmst(), "just checking");
if (BindCMSThreadToCPU && !os::bind_to_processor(CPUForCMSThread)) {
log_warning(gc)("Couldn't bind CMS thread to processor " UINTX_FORMAT, CPUForCMSThread);
}
while (!should_terminate()) {
sleepBeforeNextCycle();
if (should_terminate()) break;
GCIdMark gc_id_mark;
GCCause::Cause cause = _collector->_full_gc_requested ?
_collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
_collector->collect_in_background(cause);
}
verify_ok_to_terminate();
}
void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {
while (!should_terminate()) {
if(CMSWaitDuration >= 0) {
// Wait until the next synchronous GC, a concurrent full gc
// request or a timeout, whichever is earlier.
wait_on_cms_lock_for_scavenge(CMSWaitDuration);
} else {
// Wait until any cms_lock event or check interval not to call shouldConcurrentCollect permanently
wait_on_cms_lock(CMSCheckInterval);
}
// Check if we should start a CMS collection cycle
if (_collector->shouldConcurrentCollect()) {
return;
}
// .. collection criterion not yet met, let's go back
// and wait some more
}
}
Код для определения необходимости повторного использования находится в: /src/hotspot/share/gc/cms/concurrentMarkSweepGeneration.cpp.
bool CMSCollector::shouldConcurrentCollect() {
LogTarget(Trace, gc) log;
if (_full_gc_requested) {
log.print("CMSCollector: collect because of explicit gc request (or GCLocker)");
return true;
}
FreelistLocker x(this);
// ------------------------------------------------------------------
// Print out lots of information which affects the initiation of
// a collection.
if (log.is_enabled() && stats().valid()) {
log.print("CMSCollector shouldConcurrentCollect: ");
LogStream out(log);
stats().print_on(&out);
log.print("time_until_cms_gen_full %3.7f", stats().time_until_cms_gen_full());
log.print("free=" SIZE_FORMAT, _cmsGen->free());
log.print("contiguous_available=" SIZE_FORMAT, _cmsGen->contiguous_available());
log.print("promotion_rate=%g", stats().promotion_rate());
log.print("cms_allocation_rate=%g", stats().cms_allocation_rate());
log.print("occupancy=%3.7f", _cmsGen->occupancy());
log.print("initiatingOccupancy=%3.7f", _cmsGen->initiating_occupancy());
log.print("cms_time_since_begin=%3.7f", stats().cms_time_since_begin());
log.print("cms_time_since_end=%3.7f", stats().cms_time_since_end());
log.print("metadata initialized %d", MetaspaceGC::should_concurrent_collect());
}
// ------------------------------------------------------------------
if (!UseCMSInitiatingOccupancyOnly) {
if (stats().valid()) {
if (stats().time_until_cms_start() == 0.0) {
return true;
}
} else {
if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
log.print(" CMSCollector: collect for bootstrapping statistics: occupancy = %f, boot occupancy = %f",
_cmsGen->occupancy(), _bootstrap_occupancy);
return true;
}
}
}
if (_cmsGen->should_concurrent_collect()) {
log.print("CMS old gen initiated");
return true;
}
// We start a collection if we believe an incremental collection may fail;
// this is not likely to be productive in practice because it's probably too
// late anyway.
CMSHeap* heap = CMSHeap::heap();
if (heap->incremental_collection_will_fail(true /* consult_young */)) {
log.print("CMSCollector: collect because incremental collection will fail ");
return true;
}
if (MetaspaceGC::should_concurrent_collect()) {
log.print("CMSCollector: collect for metadata allocation ");
return true;
}
// CMSTriggerInterval starts a CMS cycle if enough time has passed.
if (CMSTriggerInterval >= 0) {
if (CMSTriggerInterval == 0) {
// Trigger always
return true;
}
// Check the CMS time since begin (we do not check the stats validity
// as we want to be able to trigger the first CMS cycle as well)
if (stats().cms_time_since_begin() >= (CMSTriggerInterval / ((double) MILLIUNITS))) {
if (stats().valid()) {
log.print("CMSCollector: collect because of trigger interval (time since last begin %3.7f secs)",
stats().cms_time_since_begin());
} else {
log.print("CMSCollector: collect because of trigger interval (first collection)");
}
return true;
}
}
return false;
}
Анализ того, запускает ли логическое суждение GC, делится на следующие ситуации:
-
Активировать CMS GC:позвонив
_collector->collect_in_background()
Запустите фоновый сборщик мусора.-
По умолчанию CMS использует статистику времени выполнения JVM, чтобы определить, нужно ли запускать CMS GC.
-XX:CMSInitiatingOccupancyFraction
Чтобы судить о значении , необходимо задать параметры-XX:+UseCMSInitiatingOccupancyOnly
. -
если включено
-XX:UseCMSInitiatingOccupancyOnly
параметр, чтобы определить, превышает ли текущее использование старой области пороговое значение, а затем запустить CMS GC, пороговое значение может быть передано через параметр-XX:CMSInitiatingOccupancyFraction
Установите его, если не установлено, по умолчанию 92%. -
Если предыдущий Young GC завершился неудачно или может произойти сбой следующего Young GC, CMS GC необходимо активировать в обоих случаях.
-
CMS не выполняет сборку мусора на MetaSpace или Perm по умолчанию, если вы хотите производить сборку мусора на этих областях, вам нужно задать параметры
-XX:+CMSClassUnloadingEnabled
.
-
-
Запустить полный сборщик мусора:Выполните полную сборку мусора напрямую. Эта ситуация описана в сценарии 7.
-
если
_full_gc_requested
Верно, указывая на то, что сборщик мусора явно необходим, например вызов System.gc. -
Не удалось выделить память для объекта или TLAB в области Eden, что привело к возникновению Young GC в
GenCollectorPolicy
Категорияsatisfy_failed_allocation()
судить по методу.
-
Вы можете посмотреть распечатку лога в исходном коде, по логу мы можем более четко узнать конкретные причины, а затем приступить к анализу.
4.5.3 Стратегия
Здесь мы по-прежнему берем наиболее распространенный сценарий достижения коэффициента восстановления.В отличие от преждевременного продвижения, эти объекты действительно выживают в течение определенного периода времени.Время выживания превышает время TP9999, но оно не может обеспечить долгосрочное выживание, например, различные базы данных, Сетевые ссылки, кеши со сроком действия и т. д.
По сути, это идея для решения такой обычной проблемы утечки памяти.Основные шаги заключаются в следующем:
Dump Diff и Leak Suspects более интуитивно понятны и не будут представлены. Вот еще несколько ключевых моментов:
- Дамп памяти:Не забудьте убрать трафик при использовании jmap, arthas и прочих дампов для снэпшотов, а заодноДамп один раз до и после возникновения CMS GC.
- Анализ верхнего компонента:Не забывайте наблюдать за гистограммой в нескольких измерениях, таких как объект, класс, загрузчик классов, пакет и т. д., и использовать исходящие и входящие для анализа связанных объектов. в.
- Анализ недостижимого:Сосредоточьтесь на этом и обратите внимание на размеры Shallow и Retained. Как показано на рисунке ниже, автор обнаружил проблему скользящего окна Hystrix на основе недостижимых объектов в предыдущей оптимизации GC.
4.5.4 Резюме
После всего процесса вы можете найти проблему, но не забудьте использовать ее в процессе оптимизации.управляющая переменнаяметод оптимизации и предотвращения некоторых изменений, которые могут усугубить проблему от маскировки.
4.6 Сценарий 6: Старый GC с одной CMS занимает много времени*
4.6.1 Феномен
Максимальное однократное STW CMS GC превышает 1000 мс и встречается нечасто.Как показано на рисунке ниже, максимальное время достигает 8000 мс. Некоторые сценарии вызывают «лавинный эффект», который очень опасен, и его следует по возможности избегать.
4.6.2 Причины
В процессе рециркуляции CMS этапом STW в основном является Init Mark и Final Remark, что также является причиной большинства CMS Old GC. также ведут к длительному времени. , но это менее распространено, и мы в основном обсуждаем первое здесь. См. Сценарий 7 для сценариев, в которых происходит деградация или фрагментация коллектора.
Чтобы понять, почему эти два этапа требуют времени, нам нужно сначала посмотреть, что они делают.
Весь основной код находится в /src/hotspot/share/gc/cms/concurrentMarkSweepGeneration.cpp , есть поток, опрашивающий ConcurrentMarkSweepThread для проверки, а детали сборки мусора в старой области полностью инкапсулированы вCMSCollector
, запись вызова вызывается ConcurrentMarkSweepThreadCMSCollector::collect_in_background
иConcurrentMarkSweepGeneration
называетсяCMSCollector::collect
метод, здесь мы обсуждаем большинство сценариевcollect_in_background
. Основным STW во всем процессе является начальная оценка и финальное замечание.Основной код находится вVM_CMS_Initial_Mark
/ VM_CMS_Final_Remark
При выполнении право на выполнение должно быть передано VMThread для выполнения.
- Этапы выполнения CMS Init Mark, реализованные в
CMSCollector::checkpointRootsInitialWork()
иCMSParInitialMarkTask::work
, общие шаги и коды следующие:
void CMSCollector::checkpointRootsInitialWork() {
assert(SafepointSynchronize::is_at_safepoint(), "world should be stopped");
assert(_collectorState == InitialMarking, "just checking");
// Already have locks.
assert_lock_strong(bitMapLock());
assert(_markBitMap.isAllClear(), "was reset at end of previous cycle");
// Setup the verification and class unloading state for this
// CMS collection cycle.
setup_cms_unloading_and_verification_state();
GCTraceTime(Trace, gc, phases) ts("checkpointRootsInitialWork", _gc_timer_cm);
// Reset all the PLAB chunk arrays if necessary.
if (_survivor_plab_array != NULL && !CMSPLABRecordAlways) {
reset_survivor_plab_arrays();
}
ResourceMark rm;
HandleMark hm;
MarkRefsIntoClosure notOlder(_span, &_markBitMap);
CMSHeap* heap = CMSHeap::heap();
verify_work_stacks_empty();
verify_overflow_empty();
heap->ensure_parsability(false); // fill TLABs, but no need to retire them
// Update the saved marks which may affect the root scans.
heap->save_marks();
// weak reference processing has not started yet.
ref_processor()->set_enqueuing_is_done(false);
// Need to remember all newly created CLDs,
// so that we can guarantee that the remark finds them.
ClassLoaderDataGraph::remember_new_clds(true);
// Whenever a CLD is found, it will be claimed before proceeding to mark
// the klasses. The claimed marks need to be cleared before marking starts.
ClassLoaderDataGraph::clear_claimed_marks();
print_eden_and_survivor_chunk_arrays();
{
if (CMSParallelInitialMarkEnabled) {
// The parallel version.
WorkGang* workers = heap->workers();
assert(workers != NULL, "Need parallel worker threads.");
uint n_workers = workers->active_workers();
StrongRootsScope srs(n_workers);
CMSParInitialMarkTask tsk(this, &srs, n_workers);
initialize_sequential_subtasks_for_young_gen_rescan(n_workers);
// If the total workers is greater than 1, then multiple workers
// may be used at some time and the initialization has been set
// such that the single threaded path cannot be used.
if (workers->total_workers() > 1) {
workers->run_task(&tsk);
} else {
tsk.work(0);
}
} else {
// The serial version.
CLDToOopClosure cld_closure(¬Older, true);
heap->rem_set()->prepare_for_younger_refs_iterate(false); // Not parallel.
StrongRootsScope srs(1);
heap->cms_process_roots(&srs,
true, // young gen as roots
GenCollectedHeap::ScanningOption(roots_scanning_options()),
should_unload_classes(),
¬Older,
&cld_closure);
}
}
// Clear mod-union table; it will be dirtied in the prologue of
// CMS generation per each young generation collection.
assert(_modUnionTable.isAllClear(),
"Was cleared in most recent final checkpoint phase"
" or no bits are set in the gc_prologue before the start of the next "
"subsequent marking phase.");
assert(_ct->cld_rem_set()->mod_union_is_clear(), "Must be");
// Save the end of the used_region of the constituent generations
// to be used to limit the extent of sweep in each generation.
save_sweep_limits();
verify_overflow_empty();
}
void CMSParInitialMarkTask::work(uint worker_id) {
elapsedTimer _timer;
ResourceMark rm;
HandleMark hm;
// ---------- scan from roots --------------
_timer.start();
CMSHeap* heap = CMSHeap::heap();
ParMarkRefsIntoClosure par_mri_cl(_collector->_span, &(_collector->_markBitMap));
// ---------- young gen roots --------------
{
work_on_young_gen_roots(&par_mri_cl);
_timer.stop();
log_trace(gc, task)("Finished young gen initial mark scan work in %dth thread: %3.3f sec", worker_id, _timer.seconds());
}
// ---------- remaining roots --------------
_timer.reset();
_timer.start();
CLDToOopClosure cld_closure(&par_mri_cl, true);
heap->cms_process_roots(_strong_roots_scope,
false, // yg was scanned above
GenCollectedHeap::ScanningOption(_collector->CMSCollector::roots_scanning_options()),
_collector->should_unload_classes(),
&par_mri_cl,
&cld_closure,
&_par_state_string);
assert(_collector->should_unload_classes()
|| (_collector->CMSCollector::roots_scanning_options() & GenCollectedHeap::SO_AllCodeCache),
"if we didn't scan the code cache, we have to be ready to drop nmethods with expired weak oops");
_timer.stop();
log_trace(gc, task)("Finished remaining root initial mark scan work in %dth thread: %3.3f sec", worker_id, _timer.seconds());
}
Весь процесс относительно прост, начиная с корня GC, чтобы пометить объекты в старом, и используя BitMap для обработки ссылки на старую область в молодой области после завершения обработки.Весь процесс в основном быстрый, и есть несколько больших пауз.
- Шаги CMS Final Remark для реализации в
CMSCollector::checkpointRootsFinalWork()
, общий код и шаги следующие:
void CMSCollector::checkpointRootsFinalWork() {
GCTraceTime(Trace, gc, phases) tm("checkpointRootsFinalWork", _gc_timer_cm);
assert(haveFreelistLocks(), "must have free list locks");
assert_lock_strong(bitMapLock());
ResourceMark rm;
HandleMark hm;
CMSHeap* heap = CMSHeap::heap();
if (should_unload_classes()) {
CodeCache::gc_prologue();
}
assert(haveFreelistLocks(), "must have free list locks");
assert_lock_strong(bitMapLock());
heap->ensure_parsability(false); // fill TLAB's, but no need to retire them
// Update the saved marks which may affect the root scans.
heap->save_marks();
print_eden_and_survivor_chunk_arrays();
{
if (CMSParallelRemarkEnabled) {
GCTraceTime(Debug, gc, phases) t("Rescan (parallel)", _gc_timer_cm);
do_remark_parallel();
} else {
GCTraceTime(Debug, gc, phases) t("Rescan (non-parallel)", _gc_timer_cm);
do_remark_non_parallel();
}
}
verify_work_stacks_empty();
verify_overflow_empty();
{
GCTraceTime(Trace, gc, phases) ts("refProcessingWork", _gc_timer_cm);
refProcessingWork();
}
verify_work_stacks_empty();
verify_overflow_empty();
if (should_unload_classes()) {
CodeCache::gc_epilogue();
}
JvmtiExport::gc_epilogue();
assert(_markStack.isEmpty(), "No grey objects");
size_t ser_ovflw = _ser_pmc_remark_ovflw + _ser_pmc_preclean_ovflw +
_ser_kac_ovflw + _ser_kac_preclean_ovflw;
if (ser_ovflw > 0) {
log_trace(gc)("Marking stack overflow (benign) (pmc_pc=" SIZE_FORMAT ", pmc_rm=" SIZE_FORMAT ", kac=" SIZE_FORMAT ", kac_preclean=" SIZE_FORMAT ")",
_ser_pmc_preclean_ovflw, _ser_pmc_remark_ovflw, _ser_kac_ovflw, _ser_kac_preclean_ovflw);
_markStack.expand();
_ser_pmc_remark_ovflw = 0;
_ser_pmc_preclean_ovflw = 0;
_ser_kac_preclean_ovflw = 0;
_ser_kac_ovflw = 0;
}
if (_par_pmc_remark_ovflw > 0 || _par_kac_ovflw > 0) {
log_trace(gc)("Work queue overflow (benign) (pmc_rm=" SIZE_FORMAT ", kac=" SIZE_FORMAT ")",
_par_pmc_remark_ovflw, _par_kac_ovflw);
_par_pmc_remark_ovflw = 0;
_par_kac_ovflw = 0;
}
if (_markStack._hit_limit > 0) {
log_trace(gc)(" (benign) Hit max stack size limit (" SIZE_FORMAT ")",
_markStack._hit_limit);
}
if (_markStack._failed_double > 0) {
log_trace(gc)(" (benign) Failed stack doubling (" SIZE_FORMAT "), current capacity " SIZE_FORMAT,
_markStack._failed_double, _markStack.capacity());
}
_markStack._hit_limit = 0;
_markStack._failed_double = 0;
if ((VerifyAfterGC || VerifyDuringGC) &&
CMSHeap::heap()->total_collections() >= VerifyGCStartAt) {
verify_after_remark();
}
_gc_tracer_cm->report_object_count_after_gc(&_is_alive_closure);
// Change under the freelistLocks.
_collectorState = Sweeping;
// Call isAllClear() under bitMapLock
assert(_modUnionTable.isAllClear(),
"Should be clear by end of the final marking");
assert(_ct->cld_rem_set()->mod_union_is_clear(),
"Should be clear by end of the final marking");
}
Final Remark — это последняя вторая метка, которая будет выполняться только в том случае, если фоновый GC выполняет этап InitialMarking.Если это этап InitialMarking, выполняемый Foreground GC, нет необходимости снова выполнять FinalRemark. Начальный этап Final Remark аналогичен процессу обработки Init Mark, но последующий обход таблицы карточек, очистка эталонного экземпляра и добавление его в эталонное обслуживаниеpend_list
, если вы хотите собрать метаданные, вам также следует очистить ресурсы, которые больше не используются в таких компонентах, как SystemDictionary, CodeCache, SymbolTable, StringTable и т. д.
4.6.3 Стратегия
Зная два процесса выполнения процесса STW, наш анализ и решение относительно просты.Поскольку большинство проблем находится в процессе Final Remark, здесь мы также возьмем эту сцену в качестве примера, основные шаги:
-
【направление】Просмотрите подробный журнал GC, найдите журнал окончательных замечаний при возникновении проблемы, проанализируйте, является ли нормальным реальное время, требующее обработки ссылок и обработки метаданных, и необходимо ли передать подробную информацию.
-XX:+PrintReferenceGC
параметр включен.В основном в журнале вы можете определить, в каком направлении проблема, и если она занимает более 10% времени, вам нужно обратить внимание.
2019-02-27T19:55:37.920+0800: 516952.915: [GC (CMS Final Remark) 516952.915: [ParNew516952.939: [SoftReference, 0 refs, 0.0003857 secs]516952.939: [WeakReference, 1362 refs, 0.0002415 secs]516952.940: [FinalReference, 146 refs, 0.0001233 secs]516952.940: [PhantomReference, 0 refs, 57 refs, 0.0002369 secs]516952.940: [JNI Weak Reference, 0.0000662 secs]
[class unloading, 0.1770490 secs]516953.329: [scrub symbol table, 0.0442567 secs]516953.373: [scrub string table, 0.0036072 secs][1 CMS-remark: 1638504K(2048000K)] 1667558K(4352000K), 0.5269311 secs] [Times: user=1.20 sys=0.03, real=0.53 secs]
-
【Основная причина】С конкретным направлением мы можем провести углубленный анализ.Вообще говоря, наиболее подверженными проблемам являются FinalReference в Reference и таблица символов очистки в обработке информации метаданных.Чтобы найти конкретный код проблемы, вам нужна память.Инструмент анализа MAT или JProfiler, обратите внимание на дамп кучи перед запуском CMS GC. Прежде чем использовать такие инструменты, как MAT, вы также можете использовать командную строку для просмотра гистограммы объекта, которая может непосредственно определить проблему.
-
Анализ ключевых наблюдений FinalReference
java.lang.ref.Finalizer
Дерево доминаторов объекта для поиска источника утечки. Есть несколько моментов, из-за которых часто возникают проблемы с SocketSocksSocketImpl
, ДжерсиClientRuntime
, MySQLConnectionImpl
и Т. Д. -
Таблица символов очистки показывает, что для очистки ссылок на символы метаданных требуется время. Ссылки на символы — это представление методов в JVM, когда код Java компилируется в байт-код. Жизненный цикл обычно такой же, как у Class.
_should_unload_classes
Когда установлено значение true наCMSCollector::refProcessingWork()
обрабатывается вместе с Class Unload, String Table.
-
if (should_unload_classes()) {
{
GCTraceTime(Debug, gc, phases) t("Class Unloading", _gc_timer_cm);
// Unload classes and purge the SystemDictionary.
bool purged_class = SystemDictionary::do_unloading(_gc_timer_cm);
// Unload nmethods.
CodeCache::do_unloading(&_is_alive_closure, purged_class);
// Prune dead klasses from subklass/sibling/implementor lists.
Klass::clean_weak_klass_links(purged_class);
}
{
GCTraceTime(Debug, gc, phases) t("Scrub Symbol Table", _gc_timer_cm);
// Clean up unreferenced symbols in symbol table.
SymbolTable::unlink();
}
{
GCTraceTime(Debug, gc, phases) t("Scrub String Table", _gc_timer_cm);
// Delete entries for dead interned strings.
StringTable::unlink(&_is_alive_closure);
}
}
-
【Стратегия】Легче справиться с первопричиной затрат времени на GC. Такого рода проблемы не возникают на большой территории одновременно, но во многих случаях один STW занимает много времени. Если влияние на бизнес является относительно большим, своевременно удаляйте трафик и проводите конкретную последующую оптимизацию.Стратегия следующая:
-
FinalReference: После нахождения источника памяти это можно решить путем оптимизации кода.Если местоположение не может быть обнаружено за короткое время, его можно увеличить
-XX:+ParallelRefProcEnabled
Параллельная обработка ссылки. -
Таблица символов: наблюдайте исторический пик использования области MetaSpace, а также ситуацию восстановления до и после каждого GC. Как правило, динамическая загрузка классов или обработка DSL не используются. Скорость использования MetaSpace не изменится.
-XX:-CMSClassUnloadingEnabled
Чтобы избежать обработки MetaSpace, JDK8 по умолчанию активирует CMSClassUnloadingEnabled, что заставит CMS попытаться выгрузить классы на этапе CMS-Remark.
-
4.6.4 Резюме
Фон CMS GC при нормальных обстоятельствах проблемы в основном сосредоточены на обработке метаданных, таких как ссылка и класс.Что касается обработки проблем эталонного класса, будь то FinalReference, SoftReference, WeakReference, основной метод заключается в том, чтобы найти подходящее время для дамп моментального снимка, а затем используйте инструменты анализа памяти для анализа. Что касается обработки классов, в настоящее время нет хорошего способа отключить переключатель выгрузки классов.
В G1 тоже есть проблема со ссылками, вы можете наблюдать Ref Proc в логе, а метод обработки аналогичен CMS.
читать далее:
Анализ и решения 9 распространенных проблем CMS GC в Java (часть 2)
7. Ссылки
- [1]».ガベージコレクションのアルゴリズムと実》Накамура Найо / Айкава Хикару
- [2]».The Garbage Collection Handbook》 Ричард Джонс / Энтони Хоскинг / Элиот Мосс
- [3].《Глубокое понимание виртуальной машины Java (3-е издание)Чжоу Чжимин
- [4].《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》
- [5].《Shipilev One Page Blog》 Шипилёв
- [6]. откройте JDK.Java.net/projects/JD…
- [7]. basics.org/ru/home/Ind…
- [8]».A Generational Mostly-concurrent Garbage Collector》Тони Принтезис/Дэвид Детлефс
- [9]».Java Memory Management White Paper》
- [10].《Всякое случается: понимание причинно-следственных связей в политике и стратегии》АА Хилл
8. Об авторе
- Xinyu: Присоединился к Meituan в 2015 году в качестве инженера по развитию бизнеса билетов на проживание в магазинах.
- Сян Мин: присоединился к Meituan в 2018 году в качестве инженера по разработке клиентских платформ.
- Сянпу: присоединился к Meituan в 2018 году в качестве инженера по разработке клиентских платформ.
9. Информация о наборе
Группа аналитики данных билетов бизнес-групп Meituan в магазинах искренне ищет небольших партнеров для повышения конкурентоспособности бизнеса во всех аспектах, от поставок, контроля, выбора, продаж и т. Д., С обработкой 100 000 уровней запросов в секунду, анализом данных 100 миллионов уровней. , и завершить бизнес с обратной связью.Massive HC, если вы заинтересованы, отправьте электронное письмо по адресуhezhiming@meituan.com, Мы свяжемся с вами как можно скорее.
Чтобы прочитать больше технических статей, подпишитесь на официальный аккаунт Meituantech в WeChat.