Устранение неполадок YGC заставило меня снова подняться!

Java

При высокой степени параллелизма проблема GC для Java-программ является типичной проблемой, и ее влияние часто еще более усиливается. Будь то «частота GC слишком высока» или «GC занимает слишком много времени», из-за проблемы Stop The World во время периода GC легко вызвать тайм-аут службы и вызвать проблемы с производительностью.

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

В этой статье я поделюсь более сложным онлайн-кейсом, когда Young GC занимает слишком много времени, и в то же время разберу точки знаний, связанные с YGC, в надежде, что вы что-то приобретете. Содержание разделено на следующие 2 части:

  • Начнем со случая, когда YGC заняло слишком много времени.
  • Резюме соответствующих точек знаний YGC

Начнем со случая, когда YGC заняло слишком много времени.

В апреле этого года, после запуска новой версии нашего рекламного сервиса, мы получили большое количество предупреждений о тайм-аутах обслуживания, из приведенной ниже диаграммы мониторинга видно, что количество тайм-аутов внезапно увеличилось на большой территории и даже достигло тысячи тайм-аутов интерфейса в течение 1 минуты. Процесс устранения этой проблемы подробно описан ниже.

image.png

Проверить мониторинг

Получив сигнал тревоги, мы впервые проверили систему мониторинга и сразу же обнаружили аномалию, из-за которой YoungGC заняло слишком много времени. Наша программа выходит в онлайн примерно в 21:50 Это видно из рисунка ниже: До выхода в онлайн YGC в основном завершается за десятки миллисекунд, но после выхода в онлайн время нахождения в YGC значительно увеличивается, а самое продолжительное даже достигает более 3 секунд.

image.png

Поскольку программа остановит мир во время YGC, а время ожидания обслуживания, установленное нашей вышестоящей системой, составляет сотни миллисекунд, предполагается, что это связано с тем, что YGC занимает слишком много времени, чтобы вызвать тайм-аут обслуживания большой области.
В соответствии с общим процессом устранения неполадок при сборке мусора мы немедленно удалили узел, а затем использовали следующую команду для создания дампа файла динамической памяти для сохранения сцены.
jmap -dump:format=b,file=heap pid
Наконец, онлайн-сервис был откатан.После отката сервис сразу вернулся в норму.Следующим шагом был однодневный процесс устранения неполадок и ремонта.

Подтвердите настройку JVM

С помощью следующей команды мы дважды проверили параметры JVM.

ps aux | grep "applicationName=adsearch"
-Xms4g -Xmx4g -Xmn2g -Xss1024K 
-XX:ParallelGCThreads=5 
-XX:+UseConcMarkSweepGC 
-XX:+UseParNewGC 
-XX:+UseCMSCompactAtFullCollection 
-XX:CMSInitiatingOccupancyFraction=80

Видно, что память кучи имеет размер 4G, новое поколение и старое поколение имеют размер 2G, а новое поколение использует сборщик ParNew.
Затем используйте команду jmap -heap pid, чтобы узнать: площадь Eden нового поколения составляет 1,6G, а области S0 и S1 — обе по 0,2G.
Этот запуск не изменяет никаких параметров, связанных с JVM, и объем запросов нашего сервиса в основном такой же, как обычно. Итак, догадайтесь: эта проблема, вероятно, связана с онлайн-кодом.

Проверить код

Возвращаясь к принципу YGC, чтобы подумать об этой проблеме, процесс YGC в основном включает следующие два шага:

1. Сканировать объекты из GC Root и отмечать уцелевшие объекты
2. Скопируйте уцелевший объект в область S1 или переместите его в область Old

Согласно приведенной ниже диаграмме мониторинга видно, что: при нормальных обстоятельствах уровень использования области выживших поддерживается на очень низком уровне (около 30 млн), но после выхода в онлайн уровень использования области выживших начал колебаться, и в лучшем случае она составляла почти полные 0,2 Гс. Более того, трудоемкость YGC в основном положительно коррелирует с коэффициентом использования области Survivor. Поэтому мы предполагаем, что должно быть все больше и больше объектов с длительным жизненным циклом, что приводит к увеличению времени, затрачиваемого на процесс аннотирования и копирования.

image.png

Вернемся к общей производительности сервиса: исходящий трафик существенно не изменился, в нормальных условиях время отклика основного интерфейса в основном находится в пределах 200 мс, а частота YGC составляет примерно каждые 8 ​​секунд.

Очевидно, что для локальных переменных их можно перерабатывать сразу после каждого YGC. Так почему же так много объектов выживают в YGC?

Далее мы блокируем подозрительный объект: глобальные переменные программы или статические переменные класса. Однако после диффинга кода, запущенного на этот раз, мы не обнаружили, что такие переменные были введены в код.

Анализировать файлы дампа памяти кучи

После того, как исследование кода не продвинулось, мы начали искать подсказки в файле памяти кучи.После использования инструмента MAT для импорта файла кучи, выгруженного на шаге 1, мы просмотрели все большие объекты в текущей куче через представление дерева Dominator. .

image.png

Сразу обнаружил, что класс NewOldMappingService занимает много места и находится через код: Этот класс находится в стороннем клиентском пакете и предоставляется товарной командой нашей компании для реализации преобразования старых и новых категорий (недавно Товарная команда находится в категории Систему необходимо преобразовать, чтобы она была совместима со старым бизнесом, необходимо сопоставить новую и старую категории).

Глядя на код дальше, обнаруживается, что в этом классе имеется большое количество статических HashMaps, которые используются для кэширования различных данных, которые необходимо использовать при преобразовании новых и старых категорий, чтобы уменьшить количество вызовов RPC и повысить эффективность конверсии.

image.png

Я изначально думал, что это было очень близко к истине проблемы, но глубокое исследование показало, что все статические переменные этого класса были инициализированы при загрузке класса.Хотя он будет занимать более 100 М памяти, он не будет быть добавлены после этого.данные. Кроме того, этот класс использовался онлайн еще в марте, а версия клиентского пакета не менялась.

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

Анализ затрат времени на обработку YGC. Справочник.

У команды мало опыта в устранении неполадок YGC, и они не знают, как проводить дальнейший анализ. Я в основном прошерстил все случаи, которые можно найти в интернете, и обнаружил, что причины сосредоточены в этих двух категориях:

1. Пометка уцелевших объектов занимает слишком много времени: например, метод Finalize класса Object перегружен, из-за чего пометка Final Reference занимает слишком много времени, или метод String.intern используется неправильно, из-за чего YGC сканировать StringTable слишком долго.
2. Чрезмерное накопление долговременных объектов: например, локальный кеш используется неправильно и накапливается слишком много уцелевших объектов, или поток блокируется из-за серьезной конкуренции блокировок, и жизненный цикл локальных переменных становится длиннее.

Для первого типа проблем вы можете отобразить отнимающую много времени ссылку на обработку GC с помощью следующих параметров -XX:+PrintReferenceGC. После добавления этого параметра видно, что время обработки разных типов обращений очень мало, поэтому этот фактор исключается.

image.png

Вернитесь к долговременному объекту для анализа

Позже мы добавили различные параметры GC, чтобы попытаться найти подсказки, но результата не было, похоже, мы устали и у нас нет идей. Из комплексного мониторинга и различных анализов: это должны быть только долгоживущие объекты, которые вызывают у нас эту проблему.
После нескольких часов ворочания маленький напарник снова нашел вторую подозрительную точку из кучи памяти МАТ.

image.png

Как видно из приведенного выше снимка экрана, класс ConfigService занял третье место среди крупных объектов, попавших в наше поле зрения, переменная ArrayList этого класса фактически содержит объекты 270W, и большинство из них являются одними и теми же элементами.
Класс ConfigService находится в стороннем пакете Apollo, но исходный код был переделан архитектурным отделом компании, из кода видно:Проблема кроется в строке 11. При каждом вызове метода getConfig элемент добавляется в список, а дедупликация не выполняется..

image.png

Наш рекламный сервис хранит большое количество конфигураций рекламной политики в apollo, и большинство запросов будут вызывать метод getConfig ConfigService для получения конфигурации, поэтому в пространства имен статических переменных постоянно добавляются новые объекты, что и вызывает эту проблему.

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

решение

Чтобы быстро убедиться, что YGC слишком долго не мог быть вызван этой проблемой, мы напрямую заменили старую версию клиентского пакета apollo на одном сервере, а затем перезапустили службу.После наблюдения в течение почти 20 минут YGC вернулся в нормальное состояние.
Наконец, мы уведомили архитектурный отдел об исправлении ошибки и перевыпустили super-pom, что полностью решило проблему.
02 Резюме соответствующих точек знаний YGC
В приведенном выше случае мы видим, что проблему YGC на самом деле сложнее устранить. По сравнению с FGC или OOM, журнал YGC очень прост, он знает только об изменениях и затратах времени на память нового поколения, в то же время сброшенная куча памяти должна быть тщательно проверена.

Кроме того, если вы не знаете процесс YGC, устранить неполадки будет сложнее. Здесь я разберу точки знаний, связанные с YGC, чтобы каждый мог более полно понять YGC.

Резюме соответствующих точек знаний YGC

5 вопросов, чтобы заново понять новое поколение

image.png

YGC выполняется в новом поколении, прежде всего необходимо знать разделение структуры кучи нового поколения. Новое поколение разделено на область Eden и две области Survivor, где Eden:from:to = 8:1:1 (соотношение можно задать параметром –XX:SurvivorRatio ), что является самым основным пониманием.

Почему появилось новое поколение?

Если генерации нет, все объекты находятся в одной области, и каждому сборщику мусора нужно сканировать всю кучу, что имеет проблему с эффективностью. После генерации частоту рециркуляции можно контролировать отдельно, и можно использовать различные алгоритмы рециркуляции, чтобы обеспечить глобальную оптимальную производительность ГХ.

Почему новое поколение использует алгоритм репликации?

Объекты в новом поколении умирают, и около 90% вновь созданных объектов могут быть быстро восстановлены.Стоимость алгоритма репликации низкая, а пространство может быть гарантировано без фрагментации. Хотя алгоритм сортировки меток также может гарантировать отсутствие фрагментации, из-за большого количества объектов, подлежащих очистке в новом поколении, требуется большое количество операций перемещения, прежде чем уцелевшие объекты будут отсортированы в объекты, подлежащие очистке, и временная сложность выше, чем у алгоритма копирования.

Зачем новому поколению нужно две области Survivor?

В целях экономии места, если используется традиционный алгоритм репликации и имеется только одна область Survivor, размер области Survivor должен быть равен размеру области Eden.В настоящее время потребление пространства составляет 8 * 2 , а два Выживших могут постоянно создавать новые объекты в области Эдема.Выжившие объекты можно передавать между Выжившими, а потребление пространства составляет 8+1+1, что, очевидно, имеет более высокий коэффициент использования пространства.

Каково реальное свободное пространство молодого поколения?

После YGC всегда есть свободная область выживших, поэтому доступный объем памяти нового поколения составляет 90%. Не удивляйтесь, если вы обнаружите, что емкость составляет всего 90% в журнале YGC или через команду jmap -heap pid для просмотра пространства нового поколения.

Как область Eden ускоряет выделение памяти?

Виртуальная машина HotSpot использует два метода для ускорения выделения памяти. Они включают указатель и TLAB (буферы локального распределения потоков).

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

Технология TLAB предназначена для многопоточности.Каждому потоку выделяется область в Eden, чтобы уменьшить конфликты блокировок при выделении памяти, ускорить выделение памяти и повысить пропускную способность.

4 типа коллекторов нового поколения

image.png

SerialGC (Serial Collector), старейшее однопоточное исполнение, подходящее для однопроцессорных сценариев.

ParNew (параллельный сборщик), который является многопоточным для последовательного сборщика, подходит для сценариев с несколькими ЦП и должен использоваться со старым сборщиком CMS.

ParallelGC (параллельный сборщик), который отличается от ParNew тем, что фокусируется на пропускной способности, может задавать желаемое время паузы и автоматически настраивает размер кучи и другие параметры по мере работы.

G1 (сборщик Garage-First), сборщик по умолчанию JDK 9 и более поздних версий, учитывает новое поколение и старое поколение, разбивает кучу на ряд регионов, не требует, чтобы блоки памяти были непрерывными, а новый генерация по-прежнему собирается параллельно.

Все вышеперечисленные сборщики используют алгоритмы репликации, которые являются эксклюзивными и останавливают мир во время выполнения.

Время срабатывания YGC

Когда в области Eden недостаточно места, срабатывает YGC. В сочетании с выделением памяти объекта нового поколения посмотрите на подробный процесс:

1. Новый объект сначала попытается разместить в стеке, если нет, попытайтесь выделить его в TLAB, в противном случае он будет выделен в старости, чтобы увидеть, соответствует ли он условиям большого объекта, и, наконец, рассмотреть возможность подачи заявки на место в TLAB. Район Эдем.

2. Если в области Эдема нет подходящего места, активируйте YGC.

3. Во время YGC обрабатываются уцелевшие объекты в области Эдема и области From Survivor.Если соблюдены условия для динамического суждения о возрасте или места в области To Survivor недостаточно, он сразу войдет в старость.Если места в пожилом возрасте недостаточно, произойдет сбой продвижения. , что приведет к сбору по старости. В противном случае уцелевший объект копируется в область To Survivor.

4. В настоящее время оставшиеся объекты в области Эдема и области Из выжившего являются мусорными объектами, которые можно напрямую стереть и переработать.

Кроме того, если сборщик CMS используется в старом поколении, для сокращения времени, отнимающего этап CMS Remark, также может быть активирован YGC, который здесь не будет подробно описан.

Процесс выполнения YGC

Алгоритм репликации, принятый YGC, в основном делится на следующие два этапа:

1. Найдите GC Roots и скопируйте объекты, на которые они ссылаются, в область S1.
2. Рекурсивно пройти объект на шаге 1, скопировать объект, на который он ссылается, в область S1 или переместить его в область Old.

Весь описанный выше процесс требует приостановки бизнес-потока (STW), но сборщики нового поколения, такие как ParNew, могут выполняться параллельно с несколькими потоками для повышения эффективности обработки.
С помощью алгоритма анализа достижимости YGC выполняет поиск вниз от корня GC (начальной точки достижимых объектов), чтобы пометить уцелевшие в настоящее время объекты, а оставшиеся неотмеченные объекты — это объекты, которые необходимо переработать.

image.png

Объекты, которые можно использовать в качестве корня GC во время YGC, включают следующее:

1. Объекты, на которые ссылается стек виртуальной машины
2. Объекты, на которые ссылаются статические свойства и константы в области метода
3. Объекты, на которые есть ссылки в собственном стеке методов
4. Объекты, удерживаемые синхронизированными замками
5. Запишите SystemDictionary текущего загруженного класса.
6. Запись StringTable, на которую ссылаются строковые константы
7. Есть объекты, на которые есть ссылки из поколения в поколение
8. Объекты в той же таблице CardTable, что и GC Root

Среди них 1-3 легко придумать, а 4-8 легко проигнорировать, но они, скорее всего, будут подсказками при анализе проблем YGC.

Кроме того, следует отметить, что в случае ссылок между поколениями на следующем рисунке объект A старого поколения также должен быть частью корня GC, но если старое поколение сканируется каждый раз, когда YGC, должны быть проблемы эффективности. В HotSpot JVM таблица карт введена для ускорения разметки ссылок между поколениями.

image.png

Таблица карт, в простом понимании, это способ изменения пространства во времени, потому что есть объекты, на которые ссылаются поколения, на долю которых приходится менее 1%, поэтому пространство кучи можно разделить на страницы карточек размером 512 байт. имеет межпоколенческую ссылку, 1 байт может использоваться для определения того, что страница карты находится в грязном состоянии, а состояние страницы карты дополнительно поддерживается технологией барьера записи.

После прохождения GC Roots вы можете найти первую партию уцелевших объектов и скопировать их в область S1. Далее идет процесс рекурсивного поиска и копирования уцелевших объектов.

В области S1 для облегчения обслуживания области памяти введены две переменные-указатели: _saved_mark_word и _top, где _saved_mark_word представляет собой расположение текущего пройденного объекта, а _top представляет собой расположение выделенной в данный момент памяти. объект между _saved_mark_word и _top Все объекты копируются, но не сканируются.

image.png

Когда Bei переходит в область S1, _top также будет двигаться вперед, пока _saved_mark_word не догонит _top, показывая, что все объекты в области S1 были пройдены.

Следует отметить одну деталь: целевое пространство копируемого объекта не обязательно является областью S1, но также может быть старым возрастом. Если возраст объекта (количество испытанных YGC) соответствует условиям определения динамического возраста, он будет напрямую переведен в старость. Возраст объекта хранится в структуре данных mark word заголовка объекта Java (если вы знакомы с одновременными блокировками Java, вы должны понимать эту структуру данных. Если вы не знакомы, рекомендуется проверить данные, чтобы понять их. , и мы не будем его здесь расширять).

последние слова

Эта статья подробно знакомит с соответствующими знаниями YGC посредством онлайн-анализа случаев и в сочетании с объяснением принципов. С точки зрения реального боя YGC, давайте кратко подытожим:
1. Прежде всего, необходимо понять принцип выполнения YGC, такой как структура кучи памяти молодого поколения, механизм выделения памяти области Eden, сканирование корней GC и процесс копирования объектов.
2. Основные этапы YGC - это маркировка и копирование.Большинство проблем YGC сосредоточено на этих двух шагах.Поэтому вы можете проверять изменения журнала YGC и памяти кучи по одному.В то же время сбрасываемая куча файлы памяти должны быть тщательно проанализированы.

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

Добро пожаловать, чтобы обратить внимание на мой общедоступный номер: [Яванская гнилая свиная кожа], чтобы получить эксклюзивные учебные ресурсы, ежедневные галантереи и благотворительные подарки.

Источник статьи:club.perf.com/article/166…