Это пятая статья JVM в серии интервью.
Расскажите о структуре памяти JVM?
Виртуальная машина Java в основном состоит из нескольких областей:
куча: куча Самая большая часть памяти в виртуальной машине Java — это область памяти, совместно используемая потоками.По сути, все массивы экземпляров объектов размещаются в куче. Область кучи подразделяется на молодое поколение в области Yound и старое поколение в области Old.Молодое поколение далее делится на три части: Eden, S0 и S1. Их соотношение по умолчанию составляет 8: 1: 1.
куча: Стек — это область памяти, приватная для нити.При выполнении каждого метода в стеке будет создан кадр стека.Вызывающий процесс метода соответствует процессу укладки в стек и извлечения из стека. Структура каждого кадра стека также включает в себя таблицу локальных переменных, стек операндов, динамическое соединение и адрес возврата метода.
Таблица локальных переменных используется для хранения параметров метода и локальных переменных. Когда вызывается первый метод, его параметры передаются в непрерывной таблице локальных переменных, начиная с 0.
Стек операндов используется для передачи некоторых инструкций байт-кода из таблицы локальных переменных в стек операндов, а также для подготовки параметров для вызовов методов и получения результатов возврата метода.
Динамическое связывание используется для преобразования метода, представленного символической ссылкой, в прямую ссылку на фактический метод.
метаданные: До Java 1.7, включая концепцию области методов, пул констант существовал в области методов (постоянная генерация), а сама область методов была логической концепцией.После 1.7 пул констант был перемещен в кучу, После 1.8 , концепция постоянной генерации была удалена (концепция области метода по-прежнему сохраняется), а методом реализации являются текущие метаданные. Он содержит метаинформацию для класса и пул констант времени выполнения.
Файл класса — это информация об определении классов и интерфейсов.
Постоянный пул времени выполнения — это представление времени выполнения постоянного пула классов и интерфейсов.
собственный стек методов: область, в основном используемая для выполнения локальных нативных методов.
счетчик команд: это также частная область потока, используемая для записи адреса инструкции байт-кода, выполняемого виртуальной машиной в текущем потоке.
Знаете ли вы процесс создания нового объекта?
Когда виртуальная машина встречает ключевое слово new, реализация определяет, был ли загружен текущий класс.Если класс не загружен, сначала выполняется механизм загрузки класса, а затем объекту выделяется место и инициализируется после завершения загрузки.
- Сначала проверьте, загружен ли текущий класс, если нет, выполните механизм загрузки класса
- Загрузка: процесс загрузки из байт-кода в двоичный поток.
- Проверка: Конечно, после завершения загрузки, конечно, необходимо проверить, соответствует ли файл класса спецификации виртуальной машины, Как и в нашем запросе интерфейса, в первую очередь, конечно, сначала нужно выполнить проверку параметров.
- Подготовка: присвоить значения по умолчанию статическим переменным и константам
- Анализ: процесс замены символической ссылки в пуле констант (цель ссылки в описании символа) на прямую ссылку (указатель или дескриптор цели и т. д.).
- Инициализация: выполните блок статического кода (cinit) для инициализации, если есть родительский класс, сначала инициализируйте родительский класс.
Ps: блоки статического кода абсолютно потокобезопасны и могут быть неявно вызваны виртуальной машиной Java только во время процесса загрузки класса!(Должна ли быть проблема в том, что статический блок кода является потокобезопасным?)
Когда класс загружен, за ним следует процесс размещения и инициализации объекта.
- Сначала выделите подходящий размер памяти для объекта.
- Затем присвойте значения по умолчанию переменным экземпляра
- Установите информацию заголовка объекта, хэш-код объекта, возраст генерации GC, информацию о метаданных и т. д.
- Выполнить инициализацию конструктора (init)
Знаете модель родительского делегирования?
Классы загрузчиков делятся сверху вниз:
- Bootstrap ClassLoader запускает загрузчик классов: по умолчанию он загружает банку в каталог JAVA_HOME/lib.
- Загрузчик класса расширения Extention ClassLoader: по умолчанию загружайте банку в каталог JAVA_HOME/lib/ext.
- Application ClassLoader Загрузчик классов приложений: например, наше веб-приложение будет загружать классы в ClassPath в веб-программе.
- User ClassLoader Пользовательский загрузчик классов: определяется пользователем
Когда мы загружаем класс, мы сначала спрашиваем, загружен ли наш родительский загрузчик, если нет, то по очереди спрашиваем вверх, если не загружен, мы пытаемся загрузить текущий класс сверху вниз, пока загрузка не будет успешной.
Какие есть алгоритмы сборки мусора?
отметка-ясно
Объекты, которые должны быть переработаны, помечаются единообразно, и все отмеченные объекты одинаково перерабатываются после завершения маркировки.Поскольку процесс маркировки должен пройти все корни GC, а процесс очистки также должен пройти все объекты в heap, эффективность алгоритма маркировки-очистки Низкая, но также вызывает проблему фрагментации памяти.
алгоритм репликации
Для решения проблемы производительности появился алгоритм копирования, который делит память на две области одинакового размера и каждый раз использует одну из них. другую область памяти. , а затем очистить текущую память, чтобы можно было решить проблемы с производительностью и фрагментацией памяти. Но в то же время приносит еще одну проблему, полезный объем памяти уменьшается вдвое!
Таким образом, родилась наша текущая общая структура памяти молодого поколения + старого поколения: Эдем+S0+S1, потому что, согласно исследованиям IBM, 98% объектов умирают, поэтому на самом деле выживших объектов не так много, нет необходимости использовать половина памяти теряется вообще, поэтому соотношение по умолчанию составляет 8: 1: 1.
Таким образом, при использовании используется только одна из областей Eden и S0S1, и каждый раз уцелевшие объекты копируются в другую неиспользуемую область Survivor, а Eden и используемый Survivor очищаются одновременно, так что трата памяти составляет всего 10%.
Если последний неиспользованный Выживший не может удерживать уцелевшие предметы, эти предметы входят в Старость.
PS: Итак, есть несколько основных вопросов, которые спросят вас, почему вы разделены на область Эдема и две области Выживших? каков эффект? Это для экономии памяти и решения проблемы фрагментации памяти.Эти алгоритмы созданы для решения проблемы.Если вы понимаете причину, вам не нужно зазубривать ее.
Отметить-организовать
Использовать алгоритм репликации для старости явно не подходит, т.к. выживаемость объектов входящих в старость относительно высока, в это время частое копирование будет сильнее влиять на производительность, а другого места не будет для нижней строки. Поэтому, согласно характеристикам старости, все уцелевшие объекты помечаются с помощью алгоритма маркировки-сортировки, все уцелевшие объекты перемещаются в один конец, а затем очищается пространство памяти за границей.
Так что же такое GC ROOT? Какие существуют GC ROOT?
Алгоритм маркировки, упомянутый выше, как отметить, живой объект или нет? Для установки счетчика ссылок на объект используется простой метод подсчета ссылок.Всякий раз, когда есть ссылка на него, счетчик равен +1, в противном случае счетчик равен -1, но этот простой алгоритм не может решить проблему циклических ссылок.
Java достигает цели маркировки уцелевших объектов с помощью алгоритма анализа достижимости. Он определяет серию GC ROOT в качестве отправной точки и начинает поиск с начальной точки вниз. Путь поиска называется цепочкой ссылок. Когда объект достигает GC ROOT без каких-либо Если цепочка ссылок подключена, объект может быть определен как пригодный для повторного использования.
Объекты, которые можно использовать в качестве GC ROOT, включают:
-
Объекты, на которые есть ссылки в стеке
-
Объекты, на которые ссылаются статические переменные и константы
-
Объект, на который ссылается собственный метод стека собственных методов
Сборщик мусора понимает? Какие сборщики мусора есть в молодом и старом поколениях?
К сборщикам мусора молодого поколения относятся Serial, ParNew и Parallel, а к старому поколению относятся Serial Old, CMS, Parallel Old и новый сборщик G1 в JDK11.
Serial: Во время сборки мусора будет использоваться однопоточная версия сборщика, STW (Stop The World), то есть другие рабочие потоки должны быть приостановлены во время сборки мусора.
ParNew: Многопоточная версия Serial для использования с CMS.
Parallel Scavenge: многопоточный сборщик мусора, который может собираться параллельно
Serial Old: Версия Serial более старого поколения, также однопоточная.
Parallel Old: старая версия Parallel Scavenge.
CMS (параллельная проверка меток): Сборщик CMS - это сборщик с целью получения кратчайшего времени паузы.По сравнению с другими сборщиками, STW имеет более короткое время и может собирать параллельно.В то же время он основан на алгоритме маркировки-развертки.Весь Процесс GC делится на 4 этапа.
- Начальная отметка: отметьте объект, с которым может быть связан GC ROOT, требуется STW
- Параллельная маркировка: процесс обхода всего графа объектов, начиная с непосредственно связанных объектов GCRoots, без STW.
- Remarking: STW требуется для исправления маркировки, которая изменилась, поскольку пользовательская программа продолжает работать во время параллельной маркировки.
- Параллельная очистка: очистка и удаление мертвых объектов, оцененных на этапе маркировки, без STW
С точки зрения всего процесса одновременная маркировка и одновременная очистка занимают больше всего времени, но не требуют остановки пользовательских потоков, в то время как начальная маркировка и повторная маркировка занимают меньше времени, но пользовательские потоки необходимо останавливать.Время паузы короткое. , и большую часть времени он может работать с пользовательским потоком.
G1 (сначала мусор): сборщик мусора G1 является сборщиком мусора по умолчанию в JDK9 и больше не различает молодое и старое поколения для сбора.
Вы понимаете принцип G1?
G1 является сборщиком по умолчанию на сервере после JDK9, и он больше не различает молодое поколение и старое поколение для сборки мусора.Он делит память на несколько регионов.Размер каждого региона можно установить с помощью -XX:G1HeapRegionSize, и размер 1~32M, для хранения больших объектов выведено понятие Humongous.Объекты превышающие половину размера Региона будут считаться большими объектами, а объекты превышающие размер всего Региона будут считаться сверхбольшие объекты и будут храниться в последовательных N. В Огромном регионе G1 будет поддерживать список приоритетов в фоновом режиме при переработке, и каждый раз регион с наибольшей прибылью будет перерабатываться первым в соответствии с разрешенным временем паузы сбора, установленным Пользователь.
Процесс переработки G1 делится на следующие четыре этапа:
- Начальная отметка: отметьте объект, с которым может быть связан GC ROOT, требуется STW
- Параллельная маркировка: процесс обхода всего графа объектов из непосредственно связанного объекта GCRoots и повторной обработки объектов, которые изменились в процессе параллельной маркировки после завершения сканирования.
- Финальная отметка: ненадолго приостановить пользовательский поток, обработать его снова, требуется STW
- Скрининг и переработка: обновите статистические данные по регионам, отсортируйте ценность и стоимость переработки для каждого региона и составьте план утилизации в соответствии с временем паузы, установленным пользователем. Затем скопируйте уцелевшие объекты Региона, которые необходимо переработать, в пустой Регион и заодно очистите старый Регион. Требуется STW
В общем, помимо параллельной маркировки, несколько других процессов все еще требуют кратковременных STW.Цель G1 — максимизировать пропускную способность с контролируемыми паузами и задержками.
Когда будут запущены YGC и FGC? Когда объект вступает в старость?
Когда новый объект подает заявку на место в памяти, если область Эдема не может удовлетворить требования к выделению памяти, срабатывает YGC, и уцелевшие объекты в области оставшегося в живых и используемая область Эдема отправляются в неиспользуемую область оставшегося в живых. достаточно места после YGC , затем напрямую введите распределение старости, если старость не может выделить место, активируйте FGC, и об исключении OOM будет сообщено после того, как FGC все еще не может быть размещен.
После YGC выжившие объекты будут скопированы в неиспользуемую область Survivor.Если область S не может быть размещена, она будет напрямую переведена в старость. Для тех объектов, которые были скопированы туда и обратно в области Survivor, настройте порог подкачки через -XX:MaxTenuringThreshold, который по умолчанию равен 15. Если количество раз превышается, он также войдет в старость.
Кроме того, имеется механизм динамического суждения о возрасте, который может продвигать пожилой возраст, не дожидаясь MaxTenuringThreshold. Если сумма размеров всех объектов одного возраста в пространстве Survivor больше половины пространства Survivor, объекты, возраст которых больше или равен этому возрасту, могут напрямую попасть в старость.
Как устранить частые проблемы с FullGC?
Лучший способ справиться с такой проблемой — проанализировать ее на конкретных примерах, если нет, просто рассказать об общих шагах анализа. Возникновение FGC может быть связано с необоснованным распределением памяти.Например, область Эдема слишком мала, что приводит к тому, что объекты часто входят в старость.В это время это можно увидеть через конфигурацию параметров запуска.Кроме того, есть могут быть утечки памяти, которые можно проверить, выполнив следующие действия:
- jstat -gcutil или проверьте журнал gc.log, чтобы проверить восстановление памяти
S0 и S1 соответственно представляют пропорцию двух областей Survivor.
E обозначает долю площади Эдема, как вы можете видеть на рисунке, используется 78%.
O представляет собой старость, M представляет собой метапространство, YGC встречается 54 раза, YGCT представляет собой совокупное время YGC, а GCT представляет собой совокупное время GC.
[GC [Начало FGC представляет тип сборки мусора
PSYoungGen: 6130K->6130K(9216K)] 12274K->14330K(19456K), 0,0034895 с представляет использование памяти до и после YGC
Times: user = 0,02, sys = 0,00, real = 0,00 с, user представляет время ЦП, потребляемое в пользовательском режиме, sys представляет время ЦП, потребляемое в режиме ядра, а real представляет время ожидания различных настенных часов.
Эти две картинки являются лишь примерами и никакого отношения не имеют.Например, по картинке можно увидеть, проводится ли ФСК, сколько времени занимает ФСК, снижена ли память старого и молодого поколений после ГК, и получить некоторые предварительные информация для вынесения решения.
- Выгрузить файл памяти для конкретного анализа, например, через команду jmap jmap -dump:format=b,file=dumpfile pid, после экспорта передатьEclipse Memory Analyzerи другие инструменты для анализа, поиска кода и исправления
Тут тоже может быть повод для вопросов, типа парение ЦП, а что заодно с FGC? Подход аналогичен
- Найдите pid текущего процесса, top -p pid -H, чтобы просмотреть использование ресурсов и найти поток
- printf "%x\n" pid, преобразовать pid потока в шестнадцатеричное, например 0x32d
- jstack pid|grep -A 10 0x32d Просмотрите журнал стека потока, проблема все еще не найдена.
- Выгрузите файл памяти для анализа с помощью таких инструментов, как MAT, найдите код и восстановите его.
Есть ли опыт настройки JVM?
Следует понимать, что целью всех настроек является достижение более высокой пропускной способности с меньшими затратами на оборудование.То же самое верно и для настройки JVM.Наилучшая производительность достигается за счет настройки сборщика мусора и распределения памяти.
Значение простого параметра
В первую очередь нужно знать значение нескольких основных параметров.
- -Xms устанавливает начальный размер кучи, -Xmx устанавливает максимальный размер кучи
- -XX:NewSize размер молодого поколения, -XX:MaxNewSize максимальное значение молодого поколения, -Xmn эквивалентно одновременной настройке -XX:NewSize и -XX:MaxNewSize на одно и то же значение
- -XX:NewRatio устанавливает соотношение молодого поколения к старому, если 3, то это означает, что отношение молодого поколения к старому составляет 1:3, а значение по умолчанию 2
- -XX: SurvivorRatio Соотношение между молодым поколением и двумя Выжившими, по умолчанию 8, что означает соотношение 8:1:1.
- -XX:PretenureSizeThreshold Когда созданный объект превышает указанный размер, непосредственно выделять объект в старом возрасте.
- -XX:MaxTenuringThreshold Устанавливает порог максимального возраста для объектов, реплицированных в Survivor, и если порог превышает порог, то он будет переведен в старость
- -XX:MaxDirectMemorySize Полный сборщик мусора запускается, когда память вне кучи, выделенная Direct ByteBuffer, достигает указанного размера.
Тюнинг
- Чтобы распечатать журнал для облегчения устранения неполадок, лучше всего включить журнал GC.Включение журнала GC мало влияет на производительность, но может помочь нам быстро устранять неполадки и обнаруживать проблемы. -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log
- Обычно устанавливайте -Xms=-Xmx, что позволяет получить фиксированный размер памяти кучи, уменьшить количество сборок мусора и затраты времени, а также сделать кучу относительно стабильной.
- -XX:+HeapDumpOnOutOfMemoryError позволяет JVM автоматически генерировать моментальный снимок памяти при переполнении памяти, что удобно для устранения неполадок.
- -Xmn устанавливает размер нового поколения, слишком маленький увеличит YGC, слишком большой уменьшит размер старого поколения, обычно устанавливается от 1/4 до 1/3 всей кучи
- Установите -XX:+DisableExplicitGC, чтобы отключить системный System.gc(), чтобы предотвратить проблемы, вызванные ручным запуском FGC по ошибке.