Рубрики
Без рубрики

JVM. Управление памятью

Косвенный доступ к памяти Если вы знакомы с низкоуровневыми языками программирования, такими как C или… Помечен jvm, java, промежуточным программным обеспечением, памятью.

Косвенный доступ к памяти

Если вы знакомы с языками программирования низкого уровня, такими как C, C++ или ASM, вы можете вспомнить, как там осуществляется управление памятью. По сути, код вашего приложения должен оценить, сколько памяти потребуется для структуры данных, попросить ОС выделить эту память в виде непрерывного блока памяти, получить доступ к “ячейкам” этого блока отдельно (подготовка/чтение/запись) и, когда больше не требуется, освободить (освободить) этот блок памяти явно. Если вы забудете освободить этот блок памяти, у вас произойдет утечка памяти, которая в конечном итоге поглотит всю имеющуюся у вас оперативную память.

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

  • ЧТО я хочу создать? Веревочка. (Мне все равно, КАК)
  • Я хочу скопировать ЧТО? Список объектов. (Мне все равно, КАК)
  • Я хочу передать ЧТО этой функции? Массив. (Мне все равно, КАК)

JVM заботится обо всех показах (за исключением массивов – вам все равно нужно знать, сколько элементов (хотя и не байтов) они должны содержать). Он оценит, сколько байтов вам требуется, и выделит эти блоки памяти для вас? Будет ли он выделять смежные блоки памяти? Ты не знаешь. Будет ли он выделять память в режиме копирования при записи? Ты не знаешь. И вам не нужно беспокоиться об этих деталях. Вместо этого беспокойтесь о том, ЧТО вы будете делать в своем коде дальше.

Пулы памяти

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

Во время выполнения ваш код попросит JVM получить доступ ко всем этим пулам по разным причинам: вызов метода (функции), создание нового объекта, повторное выполнение одного и того же алгоритма, насыщение некоторых пулов достигло 100% и так далее. Это несколько сложная система, которая делает все возможное, чтобы ваш код был удобным, не беспокоясь о ТОМ, КАК (или КОГДА) получить или освободить память.

Пулы памяти делятся на 2 группы: Куча и не куча.

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

Расположение пулов памяти

Рассмотрим изображение ниже. В нем отображается основная компоновка пулов памяти JVM.

Метки указывают, в каких случаях данные распределяются в каждом пуле.

Куча

Куча – это место, где хранятся ваши структуры данных (объекты). Это самая большая часть памяти JVM. Сама куча разделена на 2 основных раздела:

  • Молодое поколение, которое также делится на
    • Эдем
    • выживший 0
    • выживший 1
  • Старое поколение (также называемое арендованным пространством)

Молодой Ген

Эдем

Эдем – это место, где объекты изначально создаются . Они остаются в Эдеме в течение некоторого времени – до тех пор, пока в этом бассейне есть место, – а затем принимается решение либо оставить их, либо выбросить, освободив их память. Если объект отброшен – пуф – память больше не выделяется, и она освобождается (в некотором смысле). Однако, если объект все еще используется в вашем java-коде, он будет помечен меткой “выживший” и перемещен (повышен) в следующий пул памяти – Выжившие.

Выжившие

Есть 2 региона выживших: s0 и s1. Один из них обычно пуст, в то время как другой заполняется, и когда он заполняется, они переключаются, кто пуст, а кто нет. Предположим, что объекты из Эдема были повышены до S1. Все остальные объекты, которые выживут в Эдеме, останутся в S1 до тех пор, пока в пуле S1 есть место. Когда S1 заполняется, приходит инспектор и проверяет каждый объект в S1, помечая объекты, которые больше не требуются в вашем java-коде. Все объекты, которые были помечены, немедленно освобождаются. Оставшиеся объекты получают медаль за выживание в S1 и перемещаются в область S0. Тем временем все объекты, пережившие Эдем, теперь повышены до S0 до. Когда S0 заполняется, проводится та же проверка, а затем выжившие перемещаются в S1 с еще одной медалью. Когда выживший получает достаточное количество медалей, он считается долгоживущим выжившим и попадает в окончательный пул памяти, где хранятся только долгоживущие объекты: старый мир.

ОлдГен

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

Не-Куча

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

PermGen/Метапространство

PermGen (Постоянное поколение) – это регион, который использовался до java 8. Начиная с java 8, он был переименован в Metaspace, и его ограничения были сняты. PermGen хранит метаданные классов (отсюда и метапространство). Не экземпляры класса.class (они хранятся в куче вместе с другими экземплярами), а просто метаданные. Метаданные класса описывают, как называется класс, какова его структура памяти, какие у него методы, статические поля и их значения. Все статическое хранится в PermGen/Метапространстве. Когда создается загрузчик классов, он получает область памяти, выделенную в PermGen, для метаданных классов этого загрузчика классов. Каждый загрузчик классов имеет свой собственный регион, поэтому загрузчики классов не разделяют классы. PermGen – это область с фиксированным размером. Вы должны заранее сообщить JVM (если вас не устраивают значения по умолчанию), какой размер PermGen вам нужен, и, как только вы достигнете предела, будет выдано исключение OutOfMemoryException. Кроме того, это было постоянно – классы, однажды загруженные, не могли быть выгружены. Это стало проблемой, когда все больше и больше приложений, библиотек и фреймворков стали полагаться на манипуляции с байт-кодом. Начиная с Java 8 этот регион был переименован в Метапространство, и ограничение было снято. Метапространство может увеличиваться настолько, насколько ему заблагорассудится (или до тех пор, пока на хост-платформе есть память). Этот рост может быть ограничен параметрами JVM.

Кэш JIT-кода

По мере запуска JVM она продолжает выполнять одни и те же части кода снова и снова – некоторые части кода более горячие, чем другие (горячие точки). Со временем JVM записывает наборы инструкций, которые продолжают повторяться и хорошо подходят для оптимизации. Эти наборы инструкций могут быть скомпилированы в машинный код, и машинный код больше не выполняется в интерпретаторе байт-кода – теперь он выполняется непосредственно в процессоре. Это значительно повышает производительность, повышая производительность этих методов на величинах 10 или 100 секунд, а в некоторых случаях даже больше. Компиляция выполняется компилятором JIT (Точно в срок), а скомпилированный машинный код хранится в области кэша кода JIT.

ГК

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

Символ

Это пространство содержит имена полей, сигнатуры методов, кэшированные числовые значения и интернированные (кэшированные) строки. Числовые литералы времени компиляции (5, 50L, 3,14D и т. Д.) кэшируются и повторно используются JVM для сохранения памяти (литералы неизменяемы, помните? Они являются статическими окончательными). То же самое происходит и со строками. Кроме того, строки можно вводить вручную во время выполнения: если вызывается метод String.intern() , он также будет кэширован. В следующий раз, когда будет сделана ссылка на строку с тем же содержимым, вместо создания нового строкового объекта будет использоваться экземпляр интернированной строки. Если эта область начинает становиться слишком большой, это может означать, что ваш java-код интернирует слишком много разных строк.

Общее классное пространство

Это небольшая область памяти, в которой хранится файл .jsa файлы – файлы данных классов java, подготовленные для быстрой загрузки. Эта область используется для ускорения запуска JVM, особенно в той части запуска, где загружаются системные библиотеки. Это не оказывает большого влияния во время выполнения.

Компилятор

Эта область используется компилятором JIT. Это рабочая область для компилятора – в ней не хранится скомпилированный машинный код.

Регистрация

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

Аргументы

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

Внутренний

Цитирую документы Java: ” Память, которая не соответствует предыдущим категориям, таким как память, используемая анализатором командной строки, JVMTI, свойствами и т. Д.”

Другой

Цитирую документы java: ” Память, не подпадающая под другую категорию”

Поток (стек JVM)

Эта область потенциально может вызвать проблемы, особенно в тяжелых условиях эксплуатации. Эта область содержит метаинформацию потоков. Всякий раз, когда поток вызывает метод, JVM помещает сигнатуру вызываемого метода (кадр стека) в стек этого потока, расположенный в области потока (стека JVM). Ссылки на все переданные параметры метода также передаются вместе. Чем больше у вас потоков и чем глубже стеки этих потоков, тем больше памяти им потребуется в этой области. Размер стека может быть ограничен для каждого потока с помощью параметров JVM, но нет способа ограничить количество потоков. Это фактически означает, что неконтролируемое распространение потоков может истощить вашу системную память (она вне кучи, помните?).

NMT

Это крошечная область, используемая встроенным механизмом отслеживания памяти java для своих внутренних нужд. NMT – это функция, которую вы хотите включить, если у вас есть проблемы с использованием памяти. Это особенность JVM, которая позволяет нам видеть, что на самом деле происходит вне кучи, поскольку других способов надежно наблюдать за использованием памяти вне кучи нет. Однако включение NMT увеличивает производительность на ~ 10%, так что это не то, что вы, возможно, захотите использовать в реальной производственной системе на ежедневной основе.

Собственные распределения

Если вне кучи находится память, которая не хранится в областях кучи (и не ограничена пределами кучи), область собственных выделений можно рассматривать как удаленную от кучи. Это часть памяти, которой JVM вообще не управляет. Вообще . Существует очень мало способов получить доступ к этой памяти из вашего кода Java ( DirectByteBuffer или ByteBuffer.allocateDirect() ). Эта часть памяти широко используется при разработке гибридных приложений Java с использованием приложений JNI – java, которые также используют компоненты, написанные на C/C++. Это часто имеет место в высокопроизводительных приложениях Java и разработке Android, где некоторые компоненты разрабатываются в собственном коде для повышения производительности.

Размеры и ограничения пулов памяти

Куча

  • МАКСИМАЛЬНЫЙ размер по умолчанию
    • jdk1.1: 16 МБ
    • jdk1.2: 64 МБ
    • jdk1.5: Математ.мин(1 ГБ, общая оперативная память/4)
    • jdk 6u18:
      • если общая оперативная память составляет <192 МБ: общая оперативная память/2
      • если общая оперативная память >192 МБ: общая оперативная память/4 (некоторые системы: Математика.макс (256 МБ, общая оперативная память/4) )
    • jdk11 [вплоть до jdk16]: всего оперативной памяти/4
  • Конфигурация ( алгоритм , проверка: java -XX:+печать флагов финальной версии )

    • -Xmx ((например -Xmx10g g ) можно использовать для установки пользовательского максимального размера кучи
    • --XX:Максимальный процент оперативной памяти ((например, -XX:Максимальный процент оперативной памяти = 75 ) (начиная с jdk8u191; по умолчанию: 25) может использоваться для настройки максимального размера кучи в процентах от общего объема оперативной памяти
    • -XX:Максимальная фракция (например, -XX:maxramfraction=2,5 ) (начиная с jdk8u131 до jdk8u191; по умолчанию: 4) – это еще один способ настроить, какую часть общей оперативной памяти можно выделить в кучу. Это в основном x в формуле: Максимальная высота/ x . В машине с MaxRAMFraction=1 равно настройке - -Xmx16g g , MaxRAMFraction=2 равно -Xmx=8 g , MaxRAMFraction=8 равно -Xmx=2g
    • и так далее. –XX:Максимальная оперативная память ((например -XX:Максимальная оперативная память=1073741824

Молодой Ген

Некоторые конфигурации могут не работать с OOTB, поскольку политика адаптивного размера может переопределять их. Чтобы отключить ASP, используйте -XX:-Используйте адаптивную политику .

  • МАКСИМАЛЬНЫЙ размер по умолчанию
    • NewRatio=2 (2/3 кучи – Старое поколение, 1/3 – молодое поколение)
  • Конфигурация
    • -Xmn (например, -Xmn2 g ) устанавливает размер (как минимальный, так и максимальный) молодого поколения на определенное значение.
    • -ХХ: Новая редакция ((например -XX:NewRatio=3 ) определяет соотношение молодого поколения к старому поколению. Например, установка -XX:NewRatio=3 означает, что соотношение между молодым и пожилым поколением составляет 1:3. Другими словами, молодое поколение (совокупный размер пространств eden и survivor) будет составлять 1/4 от общего размера кучи. Этот параметр игнорируется, если либо Новый размер или используется MaxNewSize .
    • -ХХ: Максимальный размер ((например -XX:MaxNewSize=100 м ) задает максимальный размер молодого поколения.
Эдем и выжившие
  • МАКСИМАЛЬНЫЙ размер по умолчанию
    • Коэффициент выживаемости=8
  • Конфигурация
    • -ХХ: Выживший (например, -XX: Соотношение выживших=6 ) определяет соотношение выживших в эдеме. В этом примере соотношение равно 1:6. Другими словами, каждое пространство для выживших будет на 1/7 больше эдема и, следовательно, на 1/8 больше молодого поколения (не одна седьмая, потому что есть два места для выживших). Размер выжившего можно рассчитать по следующей формуле: Размер одного выжившего/( Соотношение выживших + 2)

Из Кучи

Большинство регионов не охвачены, что означает, что они могут расти без каких-либо ограничений. Обычно это не проблема, так как большинство из этих областей используются внутренними механизмами JVM, и утечка памяти маловероятна. Однако собственный пул памяти, используемый JNI и JNA, а также прямые буферы в коде Java, с большей вероятностью приведут к утечкам памяти здесь.

ПермГен

До jdk8

  • Максимальный размер по умолчанию
    • MaxPermSize=64m в 32-разрядных системах и 85.(3) на 64-разрядных машинах
  • Конфигурация
    • -XX:Максимальный размер (например, -XX:максимальный размер=2 g ) задает максимальный размер пула постоянной памяти

Метапространство

Начиная с jdk8 и далее

  • Максимальный размер по умолчанию
    • неограниченный
  • Конфигурация
    • -XX:Максимальный размер пространства (например, -XX:MaxMetaspaceSize=500 м ) задает максимальный размер области метапространства.

Кэш JIT-кода

  • Максимальный размер по умолчанию
    • jdk1.7 и ниже:
    • jdk1.8 и выше: с включенной многоуровневой компиляцией (по умолчанию). Когда -XX:-Многоуровневая компиляция (отключено), размер кэшированного кода уменьшается до 48 МБ
  • Конфигурация
    • -XX:Зарезервированный код кэширует (например, -XX:ReservedCodeCacheSize=100m ) можно установить максимальный размер кэша JIT-кода.
    • -XX:Очистка кэш-памяти UseCodeCacheFlushing (например, -XX:UseCodeCacheFlushing или -XX:-UseCodeCacheFlushing ), чтобы включить или отключить очистку кэша JIT при выполнении определенных условий (одним из них является полный кэш).

ГК

Неограниченный

Символ

Неограниченный

Общее Классное пространство

Неограниченный

Компилятор

Неограниченный

Регистрация

Неограниченный

Аргументы

Неограниченный

Внутренний

Неограниченный

Стеки потоков

  • Максимальный размер по умолчанию
    • -Xss1024k в 64-разрядной виртуальной машине и -Xss320k на 32-разрядных виртуальных машинах (начиная с jdk1.6)
  • Конфигурация
    • -Xss (например, -Xss200m ) или -XX: Размер стека потоков (например, -XX:ThreadStackSize= 200 м ) будет ограничивать размер одного потока. Однако избегайте использования ThreadStackSize . Установка ThreadStackSize равным 0 приведет к тому, что виртуальная машина будет использовать системные (ОС) значения по умолчанию. Нет простого способа рассчитать, какой большой стек вам может понадобиться, поэтому вы можете настроить Xss, если значения по умолчанию недостаточно.

Мониторинг

Куча

Кучу довольно легко контролировать. Использование кучи по умолчанию тщательно отслеживается, вам просто нужны инструменты для доступа к этой информации. Смотрите здесь для получения дополнительной информации.

  • jmap -куча или hsdb jmap --куча --pid отображает размеры каждой области кучи и PermGen.
  • jmap -histo (предупреждение: может замедлить работу JVM на некоторое время; вывод может быть длинным, поэтому сбросьте его в файл) принимает гистограмму всех классов в куче. Это похоже на краткое изложение свалки кучи. Здесь вы можете найти все классы, количество их экземпляров и объем кучи, которую использует каждый.
  • jstat -gc -t 1000 будет выводить данные об использовании каждой области кучи каждые 1000 миллисекунд (т.е. каждую секунду) – полезно для мониторинга в реальном времени. ### Вне кучи Трудно отслеживать память вне кучи. Рекомендуется отслеживать эту “темную сторону JVM” только при необходимости, т.Е. при возникновении проблем, связанных с памятью, которые вы пытаетесь отладить. Это потому, что метод, который обеспечивает наилучшую видимость памяти вне кучи, NMT , добавляет 2 слова к каждому malloc() , что примерно приводит к снижению общей производительности на 5-10%.

NMT может быть включен при запуске JVM либо путем передачи -XX:NativeMemoryTracking=сводка или -XX:NativeMemoryTracking=подробный параметр для java программы. Если вам не нужна очень подробная информация, резюме должно быть достаточно (количество подробных выходных данных может быть излишне большим). Когда JVM запускается с включенным NMT, вы можете использовать jcmd для

  • см. сводку текущей статистики вне кучи ( jcmd Сводка VM.native_memory ),
  • смотрите подробную текущую статистику вне кучи ( jcmd VM.native_memory подробно ),
  • установите базовый уровень записей NMT ( jcmd Базовый уровень VM.native_memory ),
  • отслеживайте различия за определенные периоды времени ( jcmd VM.native_memory detail.diff ).

Рекомендации

Написано с помощью StackEdit .

Оригинал: “https://dev.to/netikras/jvm-memory-management-2ljh”