Какие есть типы памяти в jvm

Обновлено: 05.07.2024

Для оптимальной работы приложения JVM делит память на область стека (stack) и область кучи (heap). Всякий раз, когда мы объявляем новые переменные, создаем объекты или вызываем новый метод, JVM выделяет память для этих операций в стеке или в куче.

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

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

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

Помимо того, что мы рассмотрели, существуют и другие особенности стека:

  • Он заполняется и освобождается по мере вызова и завершения новых методов
  • Переменные в стеке существуют до тех пор, пока выполняется метод в котором они были созданы
  • Если память стека будет заполнена, Java бросит исключение java.lang.StackOverFlowError
  • Доступ к этой области памяти осуществляется быстрее, чем к куче
  • Является потокобезопасным, поскольку для каждого потока создается свой отдельный стек

Эта область памяти используется для динамического выделения памяти для объектов и классов JRE во время выполнения. Новые объекты всегда создаются в куче, а ссылки на них хранятся в стеке.

Эти объекты имеют глобальный доступ и могут быть получены из любого места программы.

Эта область памяти разбита на несколько более мелких частей, называемых поколениями:

Управление памятью на Java. Модель памяти Java. Модель памяти JVM. Распределение памяти Java, Молодое поколение, Старое поколение, Постоянное поколение, Куча, Стек.

Понимание Модели памяти JVM , Управления памятью Java очень важно, если вы хотите понять работу Сборки мусора Java . Сегодня мы рассмотрим управление памятью в Java, различные части памяти JVM и то, как отслеживать и выполнять настройку сборки мусора.

Модель памяти Java (JVM)

Как вы можете видеть на приведенном выше изображении, память JVM разделена на отдельные части. На широком уровне память кучи JVM физически разделена на две части – Молодое поколение и Старое поколение .

Управление памятью на Java – Молодое поколение

Молодое поколение-это место, где создаются все новые объекты. Когда молодое поколение заполняется, производится сбор мусора. Эта сборка мусора называется Minor GC . Молодое поколение разделено на три части – Память Эдема и две Память выживших пространства.

Важные моменты, касающиеся пространств для молодого поколения:

  • Большинство вновь созданных объектов находятся в пространстве памяти Eden.
  • Когда пространство Eden заполняется объектами, выполняется незначительная сборка, и все объекты, оставшиеся в живых, перемещаются в одно из пространств, оставшихся в живых.
  • Второстепенный GC также проверяет объекты выживших и перемещает их в другое пространство выживших. Таким образом, за один раз одно из оставшихся в живых мест всегда пустует.
  • Объекты, которые сохранились после многих циклов GC, перемещаются в пространство памяти старого поколения. Обычно это делается путем установления порогового значения возраста объектов молодого поколения, прежде чем они получат право на продвижение к старому поколению.

Управление памятью в Java – Старое поколение

Память старого поколения содержит объекты, которые являются долгоживущими и сохранились после многих раундов незначительной GC. Обычно сборка мусора выполняется в памяти старого поколения, когда она заполнена. Сборка мусора старого поколения называется Major GC и обычно занимает больше времени.

Остановите Мировое событие

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

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

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

Модель памяти Java – Постоянное поколение

Пермское поколение заполняется JVM во время выполнения на основе классов, используемых приложением. Perm Gen также содержит классы и методы библиотеки Java SE. Объекты пермского поколения-это мусор, собранный в полной сборке мусора.

Модель памяти Java – Область Методов

Область методов является частью пространства в PermGen и используется для хранения структуры классов (констант времени выполнения и статических переменных) и кода для методов и конструкторов.

Модель памяти Java – Пул памяти

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

Модель памяти Java – Пул констант времени Выполнения

Пул констант времени выполнения-это представление во время выполнения для каждого класса пула констант в классе. Он содержит константы времени выполнения класса и статические методы. Пул констант времени выполнения является частью области методов.

Модель памяти Java – Память стека Java

Память стека Java используется для выполнения потока. Они содержат кратковременные значения для конкретного метода и ссылки на другие объекты в куче, на которые ссылается метод. Вы должны прочитать Разницу между стековой и кучной памятью .

Управление памятью в Java – Переключатели памяти кучи Java

Java предоставляет множество переключателей памяти, которые мы можем использовать для установки размеров памяти и их соотношений. Некоторые из часто используемых переключателей памяти являются:

-Xms Для установки начального размера кучи при запуске JVM
-Xmx Для установки максимального размера кучи.
-Xmn Для определения численности молодого поколения остальная часть пространства отводится Старому поколению.
-XX:ПермГен Для установки начального размера постоянной памяти поколения
-XX:MaxPermGen Для установки максимального размера PermGen
-XX:Выживание Для обеспечения соотношения пространства Эдема и пространства Выживших, например, если размер молодого поколения составляет 10 м, а переключатель виртуальной машины равен 5 м, то для пространства Эдема будет зарезервировано 5 м, а для обоих пространств Выживших-по 2,5 м. Значение по умолчанию равно 8.
-XX:Новое время Для обеспечения соотношения размеров старого/нового поколения. Значение по умолчанию равно 2.

В большинстве случаев вышеуказанных опций достаточно, но если вы хотите проверить и другие опции, пожалуйста, проверьте Официальную страницу опций JVM .

Управление памятью в Java – Сборка мусора Java

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

Один из основных способов сбора мусора включает в себя три этапа:

  1. Маркировка : Это первый шаг, на котором сборщик мусора определяет, какие объекты используются, а какие нет.
  2. Обычное удаление : Сборщик мусора удаляет неиспользуемые объекты и освобождает свободное пространство, которое будет выделено другим объектам.
  3. Удаление с уплотнением : Для повышения производительности после удаления неиспользуемых объектов все уцелевшие объекты можно переместить, чтобы они были вместе. Это повысит производительность выделения памяти более новым объектам.

Есть две проблемы с простым подходом к пометке и удалению.

Вышеуказанные недостатки при простом подходе являются причиной того, что Сборка мусора Java является поколенческой и у нас есть Молодое поколение и Старое поколение места в памяти кучи. Я уже объяснял выше, как объекты сканируются и перемещаются из одного пространства поколений в другое на основе второстепенного GC и Основного GC.

Управление памятью в Java – Типы сборки мусора Java

Существует пять типов сбора мусора, которые мы можем использовать в наших приложениях. Нам просто нужно использовать переключатель JVM, чтобы включить стратегию сбора мусора для приложения. Давайте рассмотрим каждый из них по очереди.

Управление памятью в Java – Мониторинг сборки мусора Java

Мы можем использовать командную строку Java, а также инструменты пользовательского интерфейса для мониторинга действий по сбору мусора в приложении. Для моего примера я использую одно из демонстрационных приложений, предоставляемых загрузками Java SE.

Если вы хотите использовать одно и то же приложение, перейдите на страницу Загрузки Java SE и загрузите Демонстрации и образцы JDK 7 и JavaFX . Пример приложения, которое я использую, это Java2Demo.jar и он присутствует в каталоге jdk1.7.0_55/demo/jfc/Java2D . Однако это необязательный шаг, и вы можете запускать команды мониторинга GC для любого приложения java.

Команда, используемая мной для запуска демонстрационного приложения, является:

jstat

Мы можем использовать jstat инструмент командной строки для мониторинга памяти JVM и операций по сбору мусора. Он поставляется со стандартным JDK, так что вам не нужно ничего делать, чтобы его получить.

Для выполнения jstat вам необходимо знать идентификатор процесса приложения, вы можете легко его получить, используя команду ps-eaf | grep java .

Последним аргументом для jstat является интервал времени между каждым выводом, поэтому он будет печатать данные о памяти и сборке мусора каждые 1 секунду.

Давайте пройдемся по каждой колонке по очереди.

  • S0C и S1C : В этом столбце показан текущий размер областей Выжившего 0 и Выжившего 1 в КБ.
  • S0 и S1U : В этом столбце показано текущее использование областей Выживший 0 и Выживший 1 в КБ. Обратите внимание, что одна из областей выживших все время пуста.
  • ЕС и ЕС : Эти столбцы показывают текущий размер и использование пространства Eden в КБ. Обратите внимание, что размер ЕС увеличивается, и как только он пересекает ЕС, вызывается Второстепенный GC и размер ЕС уменьшается.
  • OC и OU : Эти столбцы показывают текущий размер и текущее использование старого поколения в КБ.
  • PC и PU : В этих столбцах показан текущий размер и текущее использование PermGen в КБ.
  • YGC и YGC : в столбце YGC отображается количество событий GC, произошедших в молодом поколении. В столбце YGCT отображается накопленное время для операций GC для молодого поколения. Обратите внимание, что оба они увеличены в одной строке, где значение EU уменьшается из-за незначительного GC.
  • FGC и FGC : в столбце FGC отображается количество произошедших полных событий GC. В столбце FGCT отображается накопленное время для полных операций GC. Обратите внимание, что полное время GC слишком велико по сравнению с временем GC молодого поколения.
  • GCT : В этом столбце отображается общее накопленное время для операций GC. Обратите внимание, что это сумма значений столбцов GCT и GCT.

Преимущество jstat в том, что он также может быть выполнен на удаленных серверах, где у нас нет графического интерфейса. Обратите внимание, что сумма S0C, S1C и EC составляет 10 м, как указано в параметре -Xmn10m JVM.

Java VisualVM с визуальным GC

Если вы хотите видеть операции с памятью и GC в графическом интерфейсе, вы можете использовать jvisualvm инструмент. Java VisualVM также является частью JDK, поэтому вам не нужно загружать его отдельно.

Просто запустите команду jvisualvm в терминале, чтобы запустить приложение Java VisualVM. После запуска вам необходимо установить Visual GC плагин с помощью опции Tools -< Плагины, как показано на рисунке ниже.

После установки Visual GC просто откройте приложение в левой боковой колонке и перейдите в раздел Visual GC . Вы получите изображение памяти JVM и сведения о сборке мусора, как показано на рисунке ниже.

Настройка Сборки мусора Java

Настройка сборки мусора Java должна быть последней опцией, которую вы должны использовать для увеличения пропускной способности вашего приложения, и только тогда, когда вы видите снижение производительности из-за более длительных периодов времени GC, вызывающих тайм-аут приложения.

Если вы видите java.lang.OutOfMemoryError: пространство PermGen ошибки в журналах, затем попробуйте отслеживать и увеличивать пространство памяти PermGen, используя параметры-XX:PermGen и -XX:MaxPermGen JVM. Вы также можете попробовать использовать -XX:+CMSClassUnloadingEnabled и проверить, как он работает с сборщиком мусора CMS.

Если вы видите много полных операций GC, то вам следует попробовать увеличить объем памяти старого поколения.

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

Это все для модели памяти Java, управления памятью в Java и сбора мусора, я надеюсь, что это поможет вам понять память JVM и процесс сбора мусора.

JVM (виртуальная машина Java) действует как механизм времени выполнения для запуска приложений Java. JVM - это то, что фактически вызывает метод main, присутствующий в коде Java. JVM является частью JRE (Java Runtime Environment).

Java-приложения называются WORA (Write Once Run Anywhere, Пиши однажды запускай везде). Это означает, что программист может разрабатывать код Java в одной системе и ожидать, что он будет работать в любой другой системе с поддержкой Java без каких-либо настроек. Это все возможно благодаря JVM.

Когда мы компилируем файл .java, компилятор Java генерирует файлы .class (содержащие байт-код) с такими же именами классов, которые присутствуют в файле .java. Этот файл .class проходит различные этапы, когда мы его запускаем. Эти шаги вместе описывают всю JVM.


Подсистема загрузчика классов (Class Loader Subsystem)

В основном подсистема загрузчика классов отвечает за три вида деятельности.

  • загрузка (Loading)
  • связывание (Linking)
  • инициализация (Initialization)

Загрузка (Loading): загрузчик классов читает файл .class, генерирует соответствующие двоичные данные и сохраняет их в области методов. Для каждого файла .class JVM хранит следующую информацию в области методов.

  • Полностью определенное имя загруженного класса и его непосредственного родительского класса.
  • Является ли файл .class связанным с Class или Interface или Enum
  • Информация о модификаторах, переменных и методах и т. д.

После загрузки файла .class JVM создает объект типа Class для представления этого файла в памяти кучи (heap). Обратите внимание, что этот объект имеет тип Class, предопределенный в пакете java.lang. Этот объект класса может использоваться программистом для получения информации уровня класса, такой как имя класса, имя родителя, методы и информация о переменной и т. д. Чтобы получить эту ссылку на объект, мы можем использовать метод getClass() класса Object.

Примечание. Для каждого загруженного файла .class создается только один объект класса.

Связывание (Linking): выполняет проверку, подготовку и (необязательно) разрешение.

  • Проверка (Verification): обеспечивает правильность файла .class, то есть проверяет, правильно ли отформатирован этот файл и создан ли он корректным компилятором или нет. Если проверка не удалась, мы получаем исключение времени исполнения java.lang.VerifyError.
  • Подготовка (Preparation): JVM выделяет память для переменных класса и инициализирует память значениями по умолчанию.
  • Разрешение (Resolution): это процесс замены символьных ссылок типа непосредственными ссылками. Это делается путем поиска в области метода, чтобы найти ссылку на объект.

Инициализация (Initialization): на этом этапе всем статическим переменным присваиваются их значения, определенные в коде и статическом блоке (если есть). Это выполняется сверху вниз в классе и от родителя к потомку в иерархии классов.

В общем, есть три загрузчика классов:

Примечание: JVM следует принципу делегирования-иерархии для загрузки классов. Загрузчик классов системы делегирует запрос на загрузку в загрузчик классов расширения и загрузчик классов расширения делегирует запрос в загрузчик класса начальной загрузки. Если класс найден в пути начальной загрузки, класс загружается, в противном случае запрос снова передается загрузчику классов расширения, а затем загрузчику классов системы. Наконец, если загрузчик классов системы не может загрузить класс, мы получаем исключение java.lang.ClassNotFoundException во время выполнения.


Память JVM

Область метода: в области метода хранится вся информация уровня класса, такая как имя класса, имя непосредственного родительского класса, информация о методах и переменных и т. д., включая статические переменные. В JVM есть только одна область методов, и это общий ресурс.

Область кучи (heap): информация обо всех объектах хранится в области кучи. Существует также одна область кучи на JVM. Это также общий ресурс.

Область стека: для каждого потока (thread) JVM создает один стек времени исполнения, который хранится здесь. Каждый блок этого стека называется активационной записью/кадром стека, в котором хранятся вызовы методов. Все локальные переменные этого метода хранятся в соответствующем кадре. После завершения потока стек его выполнения будет уничтожен JVM. Это не общий ресурс.

Регистры компьютера: хранить адрес текущей инструкции исполнения потока. Очевидно, что каждый поток имеет отдельные регистры компьютера.

Стеки нативного метода: для каждого потока создается отдельный нативный стек. Он хранит информацию о нативных методах.


Среда исполнения

Среда исполнения выполняет .class (байт-код). Она читает байт-код построчно, использует данные и информацию, присутствующую в различных областях памяти, и выполняет инструкции. Среду исполнения можно разделить на три части:

  • Интерпретатор: он интерпретирует байт-код построчно, а затем выполняет. Недостатком здесь является то, что когда один метод вызывается несколько раз, требуется интерпретация каждый раз.
  • Just-In-Time компилятор (JIT): используется для повышения эффективности интерпретатора. Он компилирует весь байт-код и заменяет его на нативный код, поэтому всякий раз, когда интерпретатор видит повторяющиеся вызовы методов, JIT предоставляет прямой нативный код для этой части, поэтому повторная интерпретация не требуется, таким образом, эффективность повышается.
  • Сборщик мусора (Garbage Collector): уничтожает объекты, на которые нет ссылок.

Нативный интерфейс Java (JNI)

Это интерфейс, который взаимодействует с библиотеками нативных методов и предоставляет нативные библиотеки (C, C++), необходимые для выполнения. Это позволяет JVM вызывать библиотеки C/C++ и вызываться библиотеками C/C++, которые могут быть специфичными для аппаратного обеспечения.

Библиотеки нативных методов

Это коллекция нативных библиотек (C, C++), которые требуются для механизма исполнения.

Следует помнить, что это внутренние особенности HotSpot (и её opensource-версии OpenJDK). В других виртуальных машинах (например в Android) всё может быть абсолютно по-другому. Области-поколения кучи вообще зависят от используемого алгоритма сборки мусора, и могут отличаться в рамках одной и той же реализации виртуальной машины. Как было сказано в предыдущих постах, некоторые сборщики не пользуются понятием поколений совсем.

Stack – место под примитивы и ссылки на объекты (но не сами объекты). Хранит локальные переменные и возвращаемые значения функций. Здесь же хранятся ссылки на объекты пока те конструируются. Все данные в стеке – GC roots. Освобождается сразу на выходе из функции. Принадлежит потоку, размер по-умолчанию указывается параметром виртуальной машины -Xss , но при создании потока программно можно указать отличное значение. Подробнее.
PermGen – В этой области хранятся загруженные классы (экземпляры класса Class<T>). Здесь же с Java 7 хранится пул строк. Изначально размера -XX:PermSize , растет динамически до -XX:MaxPermSize . Не считается частью кучи.
Metaspace – с Java 8 заменяет permanent generation. Отличие в том, что по умолчанию metaspace ограничен только размерами доступной на машине памяти, но так же как PermGen может быть ограничен, параметром -XX:MaxMetaspaceSize .
Heap – куча, вся managed-память, в которой хранятся все пользовательские объекты. Все следующие разделы – части кучи. Параметры -Xms , -Xmn и -Xmx устанавливают начальный, минимальный и максимальный размеры хипа соответственно.
Eden, New Generation, Old Generation и другие – специфичные для сборщика мусора части кучи, поколения. Могут быть разные, но общий подход сохраняется: долго живущий объект постепенно двигается во всё более старое поколение; сборка мусора в разных поколениях происходит раздельно; чем поколение старше, тем сборка в нём реже, но и дороже. Подробнее.

Хотя устройство памяти – это детали реализации виртуальной машины, для Java-разработчика знания о них несут практическую пользу. Эти знания необходимы для передачи правильных значений параметров JVM, что в свою очередь спасает от просадок производительности GC и остановок с OutOfMemoryError .

Читайте также: