В этой серии статей объясняются распространенные вопросы о JVM в интервью. Причина, по которой эти проблемы распространены, заключается в том, что они очень просты.Для вынужденного программиста соответствующие функции и принципы JVM также должны быть знакомы на работе. Автор также терпел неоднократные неудачи и баталии в процессе интервью, и резюмировал некоторые общеизвестные моменты, которые могут не только справиться с интервью, но и помочь читателям понять схему JVM.
При использовании таких языков программирования, как C, программистам необходимо самостоятельно вручную выделять и освобождать память. В отличие от Java, в нем есть сборщик мусора, и сборщик отвечает за освобождение памяти.
В процессе выполнения Java-программ виртуальная машина Java делит управляемую ею память на несколько разных областей данных. Затем давайте кратко рассмотрим конкретный процесс выполнения Java-программы:
Сначала файл исходного кода Java (суффикс .java) будет скомпилирован компилятором Java в файл байт-кода (суффикс .class), а затем загрузчик классов в JVM загружает файлы байт-кода каждого класса. В течение всего процесса выполнения программы JVM будет использовать пространство для хранения данных и сопутствующей информации, необходимой во время выполнения программы.Это пространство обычно называется областью данных времени выполнения, которую мы часто называем ОЗУ JVM. Поэтому управление памятью, о котором мы часто говорим в Java, заключается в управлении этим пространством (как выделять и освобождать пространство памяти).
Основное содержание этой статьи:
- Разделение памяти JVM
- куча
- область метода
- постоянный пул времени выполнения
- Стек виртуальной машины Java
- собственный стек методов
- счетчик команд
- стек и куча
- прямая память
- Механизм сборки мусора вне кучи
- Загрузка класса JVM
- процесс загрузки класса
- Предопределенные загрузчики классов JVM
- Модель родительского делегирования
- Механизм родительского делегирования
- родительская делегация
- создание объекта
- Структура памяти объекта
- место доступа к объекту
Разделение памяти JVM
Область данных времени выполнения разделена на две категории: частная область потока и область общих данных. Область данных, закрытая для потока, включает счетчик программ, стек виртуальной машины и локальную область методов, а область данных, совместно используемая всеми потоками, включает кучу Java и область методов, а в области методов имеется постоянный пул.
Ниже мы вводим эти области данных по очереди.
куча
Куча используется для хранения экземпляров объектов, и все объекты и массивы размещаются в куче. Это самая большая область памяти, управляемая JVM. Куча Java — это область памяти, совместно используемая всеми потоками, которая создается при запуске виртуальной машины. Единственная цель этой области памяти — хранить экземпляры объектов, почти все экземпляры объектов и массивы выделяются здесь памятью. Куча Java является основной областью, управляемой сборщиком мусора, поэтому ее также называют кучей GC (Garbage Collected Heap).С точки зрения сборки мусора, поскольку текущие сборщики в основном используют также подразделяются на: молодое поколение и старое поколение. Новое поколение разделено на: Пространство Эдема, От выжившего, До выжившего и т.д. Целью дальнейшего деления является более эффективное использование памяти или более быстрое выделение памяти.
область метода
Как и куча Java, область методов — это область памяти, совместно используемая каждым потоком и используемая для хранения таких данных, как информация о классе, константы, статические переменные и код, скомпилированный компилятором, который был загружен виртуальной машиной.
Область метода в виртуальной машине HotSpot также часто называют永久代
, они по существу не эквивалентны. Просто потому, что команда разработчиков виртуальной машины HotSpot использует постоянное поколение для реализации области методов, чтобы сборщик мусора виртуальной машины HotSpot мог управлять такой частью памяти, как куча Java. Но это не очень хорошая идея, потому что это более склонно к проблемам с переполнением памяти. Условно говоря, поведение сборки мусора в этой области редкое, но оно не является постоянным после того, как данные попадают в область метода.
постоянный пул времени выполнения
Пул констант времени выполнения является частью области методов. В дополнение к информации описания версии класса, поля, метода, интерфейса и т. д. файл класса также содержит информацию о постоянном пуле (используется для хранения различных литералов и символических ссылок, сгенерированных во время компиляции).
Стек виртуальной машины Java
Стек виртуальной машины Java является потоко-частным, и его жизненный цикл такой же, как и у потока.Он описывает модель памяти для выполнения методов Java. Память Java можно грубо разделить на память кучи (Heap) и память стека (Stack), где стек — это стек виртуальной машины или часть таблицы локальных переменных стека виртуальной машины. Хранит такую информацию, как таблица локальных переменных, стек операндов, динамическое связывание и выход из метода. Таблица локальных переменных в основном хранит различные типы данных и ссылки на объекты, известные компилятору.
собственный стек методов
Роль, которую играет стек виртуальной машины, очень похожа, разница в том, что стек виртуальной машины служит виртуальной машине для выполнения методов Java (то есть байт-кода), а собственный стек методов обслуживает собственные методы, используемые виртуальной машиной. . Собственный метод — это интерфейс для Java-программы для вызова не-Java-кода. При определении метода Native тело реализации не предоставляется (что-то вроде определения интерфейса Java), потому что его тело реализации реализовано снаружи на языке, отличном от Java. Идентификатор native можно использовать со всеми другими идентификаторами Java, кроме абстрактных.
Мы знаем, что когда класс используется впервые, байт-код этого класса будет загружен в память, и он будет загружен только один раз. Запись загруженного байт-кода поддерживает список всех дескрипторов методов класса.Эти дескрипторы методов содержат такую информацию, как где хранится код метода, какие у него параметры, дескриптор метода (общедоступный и т. д.) и т. д.
Если в дескрипторе метода есть натив, в блоке дескриптора будет указатель на реализацию метода. Они реализованы в некоторых DLL-файлах, но загружаются операционной системой в адресное пространство Java-программы. Когда загружается класс с собственным методом, связанная с ним библиотека DLL не загружается, поэтому указатель на реализацию метода не устанавливается. Эти библиотеки DLL не загружаются до тех пор, пока не будет вызван собственный метод путем вызоваjava.system.loadLibrary()
осуществленный.
Напоминаем, что использование нативных методов сопряжено с накладными расходами и теряет многие преимущества Java. Если у нас нет выбора, мы можем использовать нативный метод.
счетчик команд
Счетчик программ — это небольшой объем памяти, который можно рассматривать как индикатор номера строки байт-кода, выполняемого текущим потоком. Когда интерпретатор байт-кода работает, он выбирает следующую инструкцию байт-кода для выполнения, изменяя значение этого счетчика.Такие функции, как ветвление, цикл, переход, обработка исключений и восстановление потока, должны полагаться на этот счетчик для завершения. Кроме того, чтобы восстановить правильную позицию выполнения после переключения потока, каждый поток должен иметь независимый программный счетчик. Счетчики между каждым потоком не влияют друг на друга и хранятся независимо. Мы называем этот тип области памяти "приватным потоком". ". Оперативная память.
стек и куча
Стек решает проблему запуска программы, то есть как программа выполняется или как обрабатывать данные; куча решает проблему хранения данных, то есть как и где данные размещаются.
В Java у потока будет соответствующий ему стек потоков, что легко понять, поскольку разные потоки выполняют логику по-разному, поэтому требуется отдельный стек потоков. Куча совместно используется всеми потоками. Поскольку стек является рабочей единицей, хранящаяся в нем информация относится к текущему потоку (или программе). Включая локальные переменные, состояние выполнения программы, возвращаемые значения метода и т. д., а куча отвечает только за хранение информации об объекте.
Куча Java — это область данных времени выполнения, из которой классы (объекты) выделяют пространство.Эти объекты создаются такими инструкциями, как new, newarray, anewarray и multianewarray, и их не нужно явно освобождать программным кодом.Куча отвечает за сборка мусора Да, преимущество кучи в том, что размер памяти может быть динамически выделен, и время жизни не нужно сообщать компилятору заранее, потому что он выделяет память динамически во время выполнения, а сборщик мусора Java будет автоматически собирать эти данные, которые больше не используются.Но недостаток в том, что скорость доступа медленнее из-за динамического выделения памяти во время выполнения.Преимущество стека в том, что скорость доступа быстрее, чем куча, уступая только регистру, и данные стека могут быть общими.Но недостатком является то, что в стеке есть данные.Размер и время жизни должны быть детерминированными и не иметь гибкости.Стек в основном хранит некоторые основные типы переменных(int, short, long, byte, float, double, boolean, char) и дескрипторы объектов.
прямая память
В Java, когда мы хотим выполнять низкоуровневые операции с данными, мы обычно оперируем в форме байтов данных.В настоящее время часто используется такой класс, как ByteBuffer. ByteBuffer предоставляет два статических метода экземпляра:
public static ByteBuffer allocate(int capacity)
public static ByteBuffer allocateDirect(int capacity)
Зачем предоставлять два пути? Это связано с механизмом использования памяти Java. Существует два типа ByteBuffer, один — heap ByteBuffer, который размещается в куче памяти JVM и непосредственно отвечает за сборку мусора виртуальной машиной Java; другой — прямой ByteBuffer, который размещается в памяти вне виртуальной машины. через ЖНИ. Недавно добавленный класс NIO (новый ввод-вывод) в JDK1.4 представляет метод ввода-вывода, основанный на канале и буфере, который может напрямую выделять память вне кучи с помощью библиотеки собственных функций, а затем работать с объектом DirectByteBuffer, хранящимся в Куча Java как ссылка на эту память. Это может значительно повысить производительность в некоторых сценариях за счет предотвращения копирования данных туда и обратно между кучей Java и собственной кучей. Выделение собственной прямой памяти не будет ограничено кучей Java, но, поскольку это память, оно будет ограничено общим объемом собственной памяти и адресным пространством процессора. Использование этой быстрой памяти не может быть просмотрено через Jmap. Его использование памяти можно увидеть только через top.
Прямая память не является частью области данных среды выполнения виртуальной машины и не является областью памяти, определенной в спецификации виртуальной машины, но эта часть памяти также часто используется. И это также может вызвать исключения OutOfMemoryError. Емкость DirectMemory может быть-XX:MaxDirectMemorySize
Указывает, если не указано, по умолчанию максимальное значение кучи Java.
Механизм сборки мусора вне кучи
прямой ByteBuffer восстанавливает память через полный сборщик мусора, прямой ByteBuffer обнаружит ситуацию и вызоветsystem.gc()
, но если используется параметр-DisableExplicitGC
Тогда быстрая память не может быть восстановлена.-XX:+DisableExplicitGC
Логотип будет автоматическиSystem.gc()
Звонок конвертируется в no-op, то есть вызов в приложенииSystem.gc()
Это станет неработоспособным, поэтому нам нужно вручную восстановить память.
@Test
public void testGcDirectBuffer() throws NoSuchFieldException, IllegalAccessException {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
Field cleanerField = buffer.getClass().getDeclaredField("cleaner");
cleanerField.setAccessible(true);
Cleaner cleaner = (Cleaner) cleanerField.get(buffer);
cleaner.clean();
}
Кроме того, CMS GC также будет восстанавливать память Direct ByteBuffer, а CMS в основном предназначена для сборки мусора пространства старого поколения.
Загрузка класса JVM
В Java загрузка типов, подключение и инициализация выполняются во время выполнения программы.Хотя эта стратегия добавит некоторые накладные расходы на загрузку классов, она обеспечивает высокую степень гибкости.Язык, который Java может естественным образом расширять динамически: характеристики динамической загрузки и динамической компоновки во время выполнения.
Виртуальная машина загружает данные, описывающие класс, из файла класса в память, проверяет, преобразует, анализирует и инициализирует данные и, наконец, формирует тип Java, который может быть непосредственно использован виртуальной машиной.Это механизм загрузки класса. виртуальной машины Java. Файл класса представляет собой строку двоичных потоков байтов. Фактически, каждый файл класса может представлять класс или интерфейс на языке Java.
процесс загрузки класса
Весь жизненный цикл класса начинается от загрузки в память виртуальной машины и до выгрузки из памяти.Весь его жизненный цикл включает в себя: Загрузка, Проверка, Подготовка, Разрешение, Инициализация и использование.(Использование) и выгрузка( Разгрузка) 7 этапов. Три части подготовки, проверки и анализа в совокупности называются связыванием.
-
нагрузка
Найдите и загрузите двоичные данные для класса. Загрузка — это первый этап процесса загрузки класса, и на этом этапе виртуальная машина должна сделать три вещи:- Получить двоичный поток байтов, определяемый полным именем класса;
- Преобразование статической структуры хранения, представленной потоком байтов, в структуру данных времени выполнения области метода;
- Создайте объект java.lang.Class, представляющий этот класс в куче Java в качестве записи доступа к данным в области методов.
-
проверять
Убедитесь в правильности загруженных классов. На этом этапе необходимо убедиться, что информация, содержащаяся в потоке байтов файла класса, соответствует спецификациям текущей виртуальной машины и не поставит под угрозу безопасность самой виртуальной машины. Содержит четыре действия проверки: проверка формата файла, проверка метаданных, проверка байт-кода и проверка ссылки на символ.- проверка формата файла
Убедитесь, что поток байтов соответствует спецификации формата файла класса и может быть обработан текущей версией виртуальной машины. Проверки могут включать следующее: начинается ли он с магического числа, находятся ли основной и дополнительный номера версий в пределах диапазона обработки виртуальной машины, не поддерживаются ли константы в пуле констант, удален ли файл или какая информация прилагается и т.д. Только двоичный поток байтов, прошедший проверку формата файла, может войти в область метода памяти для хранения, поэтому следующие три этапа проверки основаны на структуре хранения области метода и не будут оперировать потоком байтов. - проверка метаданных
Для информации, описанной байт-кодом, выполняется семантический анализ, чтобы гарантировать, что описанное содержимое соответствует требованиям спецификации языка Java. Точки проверки включают: существует ли родительский класс (кроме объекта), наследует ли родительский класс класс, который не может быть унаследован (класс, измененный final), и если класс не является абстрактным классом, реализует ли он требуемую реализацию. в его родительском классе или интерфейсе. Все ли методы, методы и поля в классе конфликтуют с родительским классом (переопределение конечного поля родительского класса, нетрадиционная перегрузка методов и т. д.). Проверка метаданных в основном предназначена для выполнения семантической проверки информации метаданных класса, чтобы гарантировать, что информация метаданных, которая не соответствует спецификации языка Java, не существует. - проверка байт-кода
Посредством анализа потока данных и потока управления семантика программы определяется как законная и логичная. Второй этап — проверка типа данных в информации метаданных.На этом этапе проверяется и анализируется тело метода класса, чтобы убедиться, что метод проверяемого класса не будет выполнять действий, угрожающих безопасности виртуальной машины. во время выполнения. .
Контрольные точки включают в себя: обеспечение того, чтобы тип данных стека операндов и последовательность кода инструкции могли работать вместе в любое время, обеспечение того, чтобы переходы инструкций не переходили в места за пределами тела метода, и обеспечение того, чтобы преобразования типов в теле метода были допустимыми. На самом деле, даже тело метода после проверки байт-кода не обязательно безопасно. - Проверка символьных ссылок
Окончательная проверка происходит, когда виртуальная машина преобразует символическую ссылку в прямую ссылку Это преобразование происходит во время третьей фазы ссылки, фазы разрешения. Проверка символьных ссылок может рассматриваться как проверка соответствия информации, отличной от самого класса (различные символические ссылки в пуле констант). Контрольная точка: может ли полное имя, описанное строкой в символьной ссылке, найти соответствующий класс, существует ли дескриптор поля, соответствующий методу в указанном классе, а также метод и поле, описанные простым именем, классом в символическая ссылка, разрешают ли права доступа к полям и методам доступ к текущему классу и т. д. Целью проверки ссылки на символ является обеспечение нормального выполнения действия синтаксического анализа.Если проверка ссылки на символ не удалась,java.lang.IncompatibleClassChangeError
подклассы исключений, такие какIllegalAccessError
,NoSuchfiledError
,NoSuchMethodError
Ждать.
- проверка формата файла
-
Подготовить
Выделите память для статических переменных класса и инициализируйте их значениями по умолчанию. Фаза подготовки — это фаза, на которой формально выделяется память для переменных класса и устанавливаются начальные значения переменных класса, все из которых будут выделены в области методов. -
Разобрать
Преобразование символической ссылки в классе в прямую ссылку. Фаза синтаксического анализа — это процесс, в котором виртуальная машина заменяет символические ссылки в пуле констант прямыми.Действия синтаксического анализа в основном выполняются для 7 типов символических ссылок, включая классы или интерфейсы, поля, методы классов, методы интерфейса, типы методов. , дескрипторы методов и квалификаторы сайта вызова. -
инициализация
Переменные класса инициализируются Присвойте правильные начальные значения статическим переменным класса, а JVM отвечает за инициализацию класса, в основном переменных класса.
Предопределенные загрузчики классов JVM
- Загрузчик классов Bootstrap
Загрузчик классов начальной загрузки — это загрузчик классов, реализованный в машинном коде, который отвечает за загрузку библиотеки классов из каталога/lib в память. Поскольку загрузчик классов начальной загрузки включает детали локальной реализации виртуальной машины, разработчики не могут напрямую получить ссылку на загрузчик классов начальной загрузки. - Стандартный загрузчик класса расширения
Загрузчик класса расширения отвечает за загрузку в память библиотеки классов в/lib/ext или в место, указанное системной переменной java.ext.dir. Разработчики могут напрямую использовать стандартный загрузчик классов расширения. - Загрузчик класса приложения (приложение)
Загрузчик классов приложений (Application ClassLoader): отвечает за загрузку библиотеки классов по пользовательскому пути (classpath).
В дополнение к этому существуют определяемые пользователем загрузчики классов, которые являются подклассами java.lang.ClassLoader. Во время работы программы файл класса динамически загружается через подкласс java.lang.ClassLoader, что отражает функцию динамической загрузки классов Java в реальном времени.
Модель родительского делегирования
Рабочий процесс модели родительского делегирования выглядит следующим образом: если загрузчик класса получает запрос на загрузку класса, он сначала не загружает класс сам, а делегирует запрос родительскому загрузчику для его завершения и так далее. Поэтому все запросы на загрузку классов должны в конечном итоге передаваться загрузчику классов запуска верхнего уровня, и только если родительский загрузчик не найдет требуемый класс, дочерний загрузчик попытается загрузить класс.
Механизм родительского делегирования
- Когда AppClassLoader загружает класс, он не пытается сначала загрузить сам класс, а делегирует запрос на загрузку класса загрузчику родительского класса ExtClassLoader для завершения.
- Когда ExtClassLoader загружает класс, он не пытается сначала загрузить сам класс, а делегирует запрос на загрузку класса BootStrapClassLoader для завершения.
- Если BootStrapClassLoader не загружается, для попытки загрузки будет использоваться ExtClassLoader;
- Если ExtClassLoader также не загружается, он будет использовать AppClassLoader для загрузки, если AppClassLoader также не загружается, будет сообщено об исключении ClassNotFoundException.
родительская делегация
Дублирования загрузки классов можно избежать за счет иерархической связи с приоритетом; Чтобы обеспечить безопасную и стабильную работу программ Java, типы, определенные основным API Java, не будут произвольно заменены.
создание объекта
Когда виртуальная машина встречает новую инструкцию, она сначала проверяет, могут ли параметры этой инструкции найти символическую ссылку этого класса в пуле констант, и проверяет, был ли загружен, разрешен и инициализирован класс, представленный этой символической ссылкой. Если нет, то сначала необходимо выполнить соответствующий процесс загрузки класса.
После прохождения проверки загрузки класса виртуальная машина выделяет память для создаваемого объекта. Размер требуемой объекту памяти можно определить после загрузки класса, а задача выделения места под объект эквивалентна делению блока памяти определенного размера из кучи Java. Существует два метода выделения: "коллизия указателей" и "свободный список":
- столкновение указателя Переместите указатель к свободному объекту на расстояние, равное размеру памяти, занимаемой объектом.
- бесплатный список Виртуальная машина поддерживает список доступных блоков памяти и выделяет достаточно большой объем памяти в списке объектов.
Выбор метода распределения определяется тем, является ли куча Java обычной или нет, а является ли куча Java обычной или нет, определяется тем, имеет ли используемый сборщик мусора функцию уплотнения. Виртуальная машина использует CAS в сочетании с неудачной повторной попыткой, чтобы обеспечить атомарность операций обновления.
Структура памяти объекта
В виртуальной машине Hotspot расположение объектов в памяти можно разделить на 3 области: заголовок объекта, данные экземпляра и заполнение выравнивания.
-
Заголовок объекта, заголовок объекта в виртуальной машине Hotspot включает в себя две части информации, первая часть используется для хранения собственных данных времени выполнения объекта (хэш, возраст генерации GC, флаг состояния блокировки и т. д.); другая часть является типом указатель, то есть объект указывает на указатель на метаданные своего класса, и виртуальная машина использует этот указатель, чтобы определить, что объект является экземпляром этого класса.
-
Данные экземпляра — это эффективная информация, которую фактически хранит объект, а также содержимое полей различных типов, определенных в программе.
-
Часть заполнения выравнивания не обязательно существует и не имеет особого значения, она действует только как заполнитель. Поскольку система автоматического управления памятью виртуальной машины Hotspot требует, чтобы начальный адрес объекта был целым числом, кратным 8 байтам, иными словами, размер объекта должен быть целым числом, кратным 8 байтам. Часть заголовка объекта точно кратна 8 байтам (1 или 2 раза), поэтому, когда часть данных экземпляра объекта не выровнена, она должна быть дополнена дополнением выравнивания.
место доступа к объекту
Создание объекта означает использование объекта.Наша программа на Java работает с конкретным объектом в куче через справочные данные в стеке. Метод доступа к объекту определяется реализацией виртуальной машины.В настоящее время основные методы доступа включают дескрипторы и прямые указатели:
- Если используется дескриптор, часть памяти будет разделена в куче Java в качестве пула дескрипторов, а адрес дескриптора объекта хранится в ссылке, а дескриптор содержит конкретную информацию об адресе данных и типе экземпляра объекта. данные;
- Прямой доступ к указателю, тогда макет объекта кучи Java должен учитывать, как предотвратить доступ к соответствующей информации типа данных, а адрес, хранящийся в ссылке, является прямым адресом объекта.
Оба этих метода доступа к объектам имеют свои преимущества. Самым большим преимуществом использования дескриптора для доступа является то, что в ссылке хранится стабильный адрес дескриптора, и при перемещении объекта изменяется только указатель данных экземпляра в дескрипторе. Сама ссылка не нуждается в изменении. Самое большое преимущество использования метода прямого доступа к указателю заключается в том, что он быстрый, что экономит время на позиционирование указателя.
резюме
В этой статье в основном рассказывается о разделении области данных времени выполнения в JVM и механизме загрузки классов. После того, как объект в JVM создан, как переработать бесполезный объект? А как насчет алгоритма сборки мусора JVM и различных сборщиков мусора? В следующей статье будет подробно рассказано.