Java работа с кучей: Управление памятью Java / Хабр

Содержание

Курс Harvard CS50 — Лекция: Стек, очередь и куча

Стек

На рисунке вы можете видеть обобщенную модель организации памяти компьютера.

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

Но важнее даже не это, а то, что стек — это структура данных, которая работает по принципу «последним пришел — первым ушел» (last in first out, LIFO). На лекции Дэвид предложил представление стека, как стопки подносов в столовой. Поднос, который попал в стопку последним, новый клиент столовой возьмет в первую очередь.

Над стеком можно осуществлять две операции: push (занесение данных) и pop (изъятие данных).

Пример реализации стека языке С приведен ниже. В этом примере, стек — это просто массив строк, имеет определенную емкость (CAPACITY), и текущий размер (size):

typedef struct {
char * strings [CAPACITY];
int size;
} stack;

Чтобы реализовать операцию push, необходимо сделать проверку, не превышает текущий размер емкость стека, после чего — вставить элемент на позицию size и увеличить size на единицу.

Для реализации операции pop, необходимо проверить, не пустой стек, уменьшить текущий размер на единицу и вернуть элемент.

Очередь

Очередь (queue) — это структура данных, которая напоминает обычную очередь. То есть, в отличие от стека, она работает по принципу «первым пришел — первым ушел» (first in first out, FIFO).

Для очереди определены две операции: добавление элемента в конец очереди (enqueue) и изъятие элемента с начала очереди (dequeue).

В примере объявлена очередь, которая, по сути, представляет собой массив строк:

typedef struct {
  int head;
  char * strings [CAPACITY]
  int size;
} queue;

Чтобы реализовать операцию enqueue, необходимо убедиться, что очередь, не переполнена, добавить элемент в конец очереди и увеличить текущий размер на единицу.

Чтобы реализовать операцию dequeue, надо убедиться, что очередь не пуста, увеличить head на единицу, уменьшить текущий размер и вернуть первый элемент очереди.

Куча и переполнение буфера

Куча

(heap) — область памяти, которую контролируют непосредственно программисты. Над которой вы, как программисты, получаете непосредственный контроль. Память здесь выделяется в результате вызова функции malloc.

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

Буфер — это массив определенной длины, расположенный в памяти. Переполнение буфера (buffer overflow) возникает, если мы пытаемся записать в него больше данных, чем предусмотрено размером этого массива. С помощью переполнения буфера злоумышленник может записать опасный код в память компьютера.

Куча Java против стека — 7 самых удивительных вещей, которые вы должны знать

Разница между кучей Java и стеком

Виртуальная машина Java (JVM), которая выделяет часть памяти из операционной системы, JVM использует эту память для создания объектов и экземпляров, и эта память называется кучей Java. Куча Java используется в качестве динамического выделения памяти. Он в основном расположен внизу адреса, и мусор собирается при заполнении кучи. Локальные переменные, которые будут сохранены, и вызовы методов присутствуют в указанной памяти, которая называется Stack. Память стека соответствует правилу «первым пришел-первым вышел» (LIFO). Стек называется статическим распределением памяти. Размер стека памяти меньше по сравнению с размером кучи памяти.

Давайте подробнее изучим Java Heap и Stack:

  • Куча Java разделена на две основные части: пространство Юнга и старое пространство. Молодое пространство является частью памяти кучи Java, которая выделяется или защищается для хранения создания нового объекта. Когда это пространство заполняется и действует в течение определенного периода, а теперь оно не используется, оно перемещается в другие части, которые являются Старым пространством, которое зарезервировано для захвата старых объектов.
  • В java Heap сборка мусора — это процесс очистки объектов, которые мертвы или не используются, что помогает освободить пространство из кучи и освободить место для новых объектов или экземпляров.
  • Пока метод вызывается, его кадр стека помещается на вершину стека вызовов. Фрейм стека содержит состояние метода, в котором выполняются определенные строки кода и все локальные переменные. Текущий метод выполнения стека всегда является методом, находящимся на вершине стека.
  • Блок был создан в стеке, когда метод вызывается для хранения значений и ссылки на объект методов. После выполнения метода блок больше не используется и становится свободным, что может быть доступно для следующего метода.
  • Стек используется для выполнения потоков. Каждый поток имеет стек виртуальных машин Java, а в стеке JVM хранятся фреймы. Методы выделены для стековой памяти, и доступ к памяти действительно быстрый. Мы не можем изменить стек виртуальной машины Java, это можно сделать только с помощью push и pop в стеке Java. Стек Java становится все больше и меньше по мере того, как push и pop работают с локальными переменными. JVM играет свою роль в вызове и возврате метода. Проще говоря, Java Stack предназначен для хранения методов и переменных.

Сравнение лицом к лицу между кучей Java и стеком (инфографика)

Ниже приведены 7 лучших сравнений Java Heap и Stack.

Ключевая разница между кучей Java и стеком

Ниже приведены некоторые моменты, которые показывают разницу между Java Heap и Stack.

  1. Java Heap — это раздел памяти, в котором элементы могут храниться и удаляться в любом порядке. В стеке элементы могут храниться и удаляться в соответствии с правилами «первым пришел — первым вышел» (LIFO).
  2. Когда куча Java полностью занята, она выбрасывает ошибку памяти или пространство кучи Java. Когда память стека занята, выдается ошибка переполнения стека.
  3. Для Java Heap, опция виртуальной машины Java Xms и Xmx может использоваться для определения начального размера и максимального размера. Для стека Java Xss JVM может использоваться для определения размера стековой памяти.
  4. Когда новый объект создан, он просто сохраняется в куче памяти Java. Ссылка на новый объект была сохранена в памяти стека.
  5. Куча Java может использоваться, когда пользователь не имеет представления о количестве данных, необходимых во время выполнения. Стек может использоваться, когда пользователь точно знает объем данных, требуемых до времени компиляции.
  6. В Heap нет никакой зависимости от какого-либо элемента для доступа к другим элементам. Любой элемент может быть доступен случайным образом в любое время. В стеке есть особый порядок доступа к элементу.
  7. Куча более сложна, так как иногда она не может знать, занята ли память или свободна. В стеке это просто и легко.

    Рекомендуемые курсы

    • Онлайн курс по структурам данных и алгоритмам
    • Сертификационный курс по управлению дефектами
    • Онлайн сертификационный курс по программированию в Shell на Cygwin

Java Heap vs Сравнительная таблица стеков

Ниже приведена таблица сравнения между кучей и стеком Java.

ОСНОВА ДЛЯ

СРАВНЕНИЕ

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

Заключение — Java Heap vs Stack

Java Heap и Stack являются частями управления памятью для системы. Он играет решающую роль при разработке и реализации приложения. Знание управления памятью необходимо для человека, прежде чем приступить к работе над живыми проектами. Это делает ваше приложение быстрее и проще в управлении.

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

Рекомендуемая статья

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

  1. Что мы должны предпочесть JavaScript Apply или Call
  2. Apache Nifi против Apache Spark — 9 полезных сравнений для изучения
  3. JavaScript против Ruby — 7 полезных сравнений для изучения
  4. Лучшие 15 вещей, которые нужно знать о MapReduce vs Spark
  5. Java против JavaScript — 8 полезных отличий для изучения

Основные принципы программирования: стек и куча

Рассказывает Аарон Краус 


Мы используем всё более продвинутые языки программирования, которые позволяют нам писать меньше кода и получать отличные результаты. За это приходится платить. Поскольку мы всё реже занимаемся низкоуровневыми вещами, нормальным становится то, что многие из нас не вполне понимают, что такое стек и куча, как на самом деле происходит компиляция, в чём разница между статической и динамической типизацией, и т.д. Я не говорю, что все программисты не знают об этих понятиях — я лишь считаю, что порой стоит возвращаться к таким олдскульным вещам. 

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

Стек

Стек — это область оперативной памяти, которая создаётся для каждого потока. Он работает в порядке LIFO (Last In, First Out),  то есть последний добавленный в стек кусок памяти будет первым в очереди на вывод из стека. Каждый раз, когда функция объявляет новую переменную, она добавляется в стек, а когда эта переменная пропадает из области видимости (например, когда функция заканчивается), она автоматически удаляется из стека. Когда стековая переменная освобождается, эта область памяти становится доступной для других стековых переменных.

Из-за такой природы стека управление памятью оказывается весьма логичным и простым для выполнения на ЦП; это приводит к высокой скорости, в особенности потому, что время цикла обновления байта стека очень мало, т.е. этот байт скорее всего привязан к кэшу процессора. Тем не менее, у такой строгой формы управления есть и недостатки. Размер стека — это фиксированная величина, и превышение лимита выделенной на стеке памяти приведёт к переполнению стека. Размер задаётся при создании потока, и у каждой переменной есть максимальный размер, зависящий от типа данных. Это позволяет ограничивать размер некоторых переменных (например, целочисленных), и вынуждает заранее объявлять размер более сложных типов данных (например, массивов), поскольку стек не позволит им изменить его. Кроме того, переменные, расположенные на стеке, всегда являются локальными.

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

Куча

Куча — это хранилище памяти, также расположенное в ОЗУ, которое допускает динамическое выделение памяти и не работает по принципу стека: это просто склад для ваших переменных. Когда вы выделяете в куче участок памяти для хранения переменной, к ней можно обратиться не только в потоке, но и во всем приложении. Именно так определяются глобальные переменные. По завершении приложения все выделенные участки памяти освобождаются. Размер кучи задаётся при запуске приложения, но, в отличие от стека, он ограничен лишь физически, и это позволяет создавать динамические переменные.

Вы взаимодействуете с кучей посредством ссылок, обычно называемых указателями — это переменные, чьи значения являются адресами других переменных. Создавая указатель, вы указываете на местоположение памяти в куче, что задаёт начальное значение переменной и говорит программе, где получить доступ к этому значению. Из-за динамической природы кучи ЦП не принимает участия в контроле над ней; в языках без сборщика мусора (C, C++) разработчику нужно вручную освобождать участки памяти, которые больше не нужны. Если этого не делать, могут возникнуть утечки и фрагментация памяти, что существенно замедлит работу кучи.

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

Заключение

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

Перевод статьи «Programming Concepts: The Stack and the Heap»

%d0%a1%d1%82%d0%b5%d0%ba %d0%b8 %d0%ba%d1%83%d1%87%d0%b0 — artemme/java-interview Wiki

  • Куча используется всеми частями приложения в то время как стек используется только одним потоком исполнения программы.
  • Всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека содержится ссылка на него. Память стека содержит только локальные переменные примитивных типов и ссылки на объекты в куче.
  • Объекты в куче доступны с любой точки программы, в то время как стековая память не может быть доступна для других потоков.
  • Управление памятью в стеке осуществляется по схеме LIFO.
  • Стековая память существует лишь какое-то время работы программы, а память в куче живет с самого начала до конца работы программы.
  • Мы можем использовать -Xms и -Xmx опции JVM, чтобы определить начальный и максимальный размер памяти в куче. Для стека определить размер памяти можно с помощью опции -Xss .
  • Если память стека полностью занята, то Java Runtime бросает java.lang.StackOverflowError, а если память кучи заполнена, то бросается исключение java.lang.OutOfMemoryError: Java Heap Space.
  • Размер памяти стека намного меньше памяти в куче. Из-за простоты распределения памяти (LIFO), стековая память работает намного быстрее кучи.

Heap

В куче хранится объекты.

  • Молодая генерация
  • Старая генеарция
  • Permanent generator(в java 8 заменена на Metaspace) — специальное пространство в котором хранятся все статические классы, ссылки и методы.
  • Java String pool — для хранения строк (можно записать туда стоку вызовом intern())

Стек

Стек вызовов — это стек LIFO хранящий информацию для возврата управления из программ в программу.
Стек в java.

  • Каждый поток имеет свой собственный стек.
  • На каждый вызво метода создается Frame, кладется в стек.

Frame содержит :

  • сслыку на пулл констант текущего класса
  • массив локальных переменных
  • стек операндов.

Сборщик мусора Garbage-First (G1) в Java VM

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

  • Размеры кучи составляют до десятков ГБ или более, причем более 50% кучи Java занято живыми данными.
  • Темпы размещения объектов и продвижения, которые могут значительно меняться со временем.
  • Значительное количество фрагментации в куче.
  • Предсказуемые целевые значения времени паузы, которые не длиннее нескольких сотен миллисекунд, избегая длительных пауз сборки мусора.

G1 заменяет коллектор Concurrent Mark-Sweep (CMS). Это также сборщик по умолчанию.

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

Включение G1

Сборщик мусора Garbage-First является сборщиком по умолчанию, поэтому обычно вам не нужно выполнять никаких дополнительных действий. Вы можете явно включить его, указав -XX:+UseG1GC в командной строке.

Основные понятия

G1 — это работающий с поколениями объектов, инкрементный, параллельный, преимущественно конкурентный, «останавливающий мир» (stop-the-world) и эвакуационный сборщик мусора, который отслеживает цели времени паузы в каждой из остановок stop-the-world. Подобно другим сборщикам, G1 разделяет кучу на (виртуальное) молодое и старое поколения. Усилия по освоению пространства сосредоточены на молодом поколении там, где это наиболее эффективно, а в старшем поколении время от времени — на освоении пространства.

Некоторые операции всегда выполняются в паузах stop-the-world для повышения пропускной способности. Другие операции, которые занимают больше времени при остановке приложения, такие как операции с целой кучей, такие как глобальная маркировка, выполняются параллельно и конкурентно с приложением. Чтобы сделать короткие паузы stop-the-world короткими для регенерации пространства, G1 выполняет регенерацию пространства поэтапно, поэтапно и параллельно. G1 достигает предсказуемости, отслеживая информацию о предыдущем поведении приложения и паузах сбора мусора, чтобы построить модель связанных затрат. Он использует эту информацию для оценки работы, выполненной в паузах. Например, G1 сначала освобождает пространство в наиболее эффективных областях (то есть областях, которые в основном заполнены мусором, поэтому и называется Garbage-First (дословно Мусор-Первый) — из-за первоочередной работы с простраствами заполненными мусором).

G1 восстанавливает пространство в основном с помощью эвакуации: живые объекты, найденные в выбранных областях памяти для сбора, копируются в новые области памяти, сжимая их в процессе. После завершения эвакуации пространство, ранее занимаемое живыми объектами, повторно используется для распределения приложением.

Сборщик Garbage-First — это не сборщик в реальном времени. Он пытается достичь поставленных целей времени паузы с высокой вероятностью в течение более длительного времени, но не всегда с абсолютной уверенностью для данной паузы.

Разметка кучи

G1 разделяет кучу на набор областей кучи одинакового размера, каждый из которых представляет собой непрерывный диапазон виртуальной памяти, как показано на рисунке. Регион — это единица выделения памяти и восстановления памяти. В любой момент времени каждый из этих регионов может быть пустым (светло-серый) или назначен определенному поколению, молодому или старому. Когда поступают запросы на память, менеджер памяти раздает свободные регионы. Диспетчер памяти назначает их поколению, а затем возвращает их приложению как свободное пространство, в которое оно может производить выделение памяти под данные.

Молодое поколение содержит районы eden (красный) и районы выживших (survivor) (красный с «S»). Эти области обеспечивают ту же функцию, что и соответствующие смежные пространства в других коллекторах, с той разницей, что в G1 эти области обычно располагаются в виде непрерывного шаблона в памяти. Старые регионы (светло-голубые) составляют старое поколение. Области старого поколения могут быть огромными (светло-голубой с буквой «H») для объектов, которые охватывают несколько областей.

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

Цикл сбора мусора

На верхнем уровне коллектор G1 чередуется между двумя фазами. Фаза «только для молодых» содержит сборки мусора, которые постепенно заполняют доступную в настоящее время память объектами старого поколения. На этапе освоения пространства G1 постепенно восстанавливает пространство в старом поколении, в дополнение к работе с молодым поколением. Затем цикл возобновляется с фазы «только для молодых».

В следующем списке подробно описаны фазы, их паузы и переход между фазами цикла сборки мусора G1:

  1. Фаза «только для молодых»: эта фаза начинается с нескольких обычных сборок молодых объектов, которые продвигают объекты в старое поколение. Переход между фазой «только для молодых» и фазой освоения пространства начинается, когда занятость старого поколения достигает определенного порогового значения, порогового значения Initiating Heap Occupancy. В это время G1 планирует Конкурентное начало молодой сборки вместо обычной молодой сборки.
    • Конкурентное начало (Concurrent Start): этот тип сборки запускает процесс маркировки в дополнение к выполнению обычной молодой сборки. Одновременная маркировка определяет все доступные в настоящее время (живые) объекты в регионах старого поколения, которые будут сохранены для следующей фазы освоения пространства. Хотя маркировка сборки еще не закончена, могут появиться обычные молодые сборки. Маркировка заканчивается двумя специальными паузами stop-the-world: Remark и Cleanup.
    • Примечание (Remark). Эта пауза завершает саму маркировку, выполняет глобальную обработку ссылок и выгрузку классов, восстанавливает полностью пустые области и очищает внутренние структуры данных. Между Remark и Cleanup G1 вычисляет информацию, чтобы впоследствии иметь возможность конкурентно освободить свободное место в выбранных областях старого поколения, что будет завершено в паузе Cleanup.
    • Очистка (Cleanup): эта пауза определяет, будет ли на самом деле фаза восстановления пространства. Если наступает этап освоения пространства, этап «только для молодых» завершается одной сборкой Prepare Mixed young.
  2. Этап восстановления пространства. Этот этап состоит из нескольких смешанных сборок, которые в дополнение к регионам молодого поколения также эвакуируют живые объекты из наборов областей старого поколения. Фаза восстановления пространства заканчивается, когда G1 определяет, что эвакуация большего количества областей старого поколения не даст достаточно свободного места, чтобы стоить усилий.

После восстановления пространства цикл сбора возобновляется с другой фазой «только для молодых». В качестве резервной копии, если приложению не хватает памяти при сборе информации о живучести, G1, как и другие сборщики, выполняет на месте полное уплотнение кучи (Full GC).

Паузы для сбора мусора и набор для сбора

G1 выполняет сборку мусора и восстановление пространства в паузах stop-the-world. Живые объекты обычно копируются из исходных областей в одну или несколько областей назначения в куче, и существующие ссылки на эти перемещенные объекты корректируются.

Для не-огромных (non-humongous) областей область назначения для объекта определяется из исходной области этого объекта:

  • Объекты молодого поколения (районы eden и survivor) копируются в survivor или старые регионы, в зависимости от их возраста.
  • Объекты из старых регионов копируются в другие старые регионы.
  • Объекты в огромных регионах обрабатываются по-разному. G1 только определяет их жизнеспособность, и, если они не живы, восстанавливает пространство, которое они занимают. Объекты в пределах огромных областей никогда не перемещаются G1.

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

  • На этапе «только для молодых» набор сбора состоит только из областей молодого поколения и огромных областей с объектами, которые потенциально могут быть восстановлены.
  • На этапе «Восстановление пространства» набор сбора состоит из областей молодого поколения, огромных областей с объектами, которые потенциально могут быть восстановлены, и некоторых областей старого поколения из набора областей-кандидатов набора сбора.

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


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


А почему бы не аллоцировать на стеке?

В самом деле: почему? Вот мы прогнали escape analysis на коде метода, обнаружили, что такие-то создаваемые объекты за пределы метода не утекают. Зачем мы их скаляризуем, почему бы нам просто не аллоцировать их на стеке? Ведь скаляризация, на первый взгляд, сложнее аллокации на стеке: отследить, какие операции с полями объектов к именно какому объекту в этот момент относятся, завести под эти поля локальные переменные, преобразовать код, чтобы он обращался к этим переменным, а не к полям объекта, приготовить задел для деоптимизации… Зачем нам весь этот геморрой, когда можно просто выделить кусок памяти на стеке, и впечатать туда объект? И мы получим полноценную ссылку на него, ничем не отличающуюся от ссылки на объект в куче. Ссылку, которую можно запихнуть в любую дырку, куда пролезет обычная ссылка (ну, слишком-то глубоко мы ее не запихаем — мы же знаем, что за пределы метода она не уйдет).

Насколько я понимаю, одного простого ответа на это «почему» нет. Более того, существовали прототипы стековой аллокации — в одной из статей по EA авторы упоминают, что их прототип делал по результатам EA именно стековую аллокацию (правда, тот прототип был на базе IBM JVM).

Но стековая аллокация не так уж проста, когда речь заходит о деталях: JVM (Oracle/Open JDK) знает, сколько памяти выделено ей под кучу — эта информация задается при запуске, и не может быть изменена. И VM этим пользуется: сразу же при старте резервирует у ОС диапазон адресов размером с максимальную кучу (не аллоцирует память, а просто резервирует адреса HEAP_START..HEAP_END = -Xmx реальная же аллокация происходит по необходимости). И с самого первого момента жизни у JVM есть эти два числа [HEAP_START..HEAP_END], между которыми должно лежать значение любого указателя на java-объект, и JVM активно полагается на то, что куча это непрерывный кусок памяти [HEAP_START..HEAP_END]. Например, эти числа можно вклеивать в генерируемый код прямо в виде констант.

Или, например, card marking. Я уже как-то писал о нем в контексте concurrency: generational GC должен как-то отслеживать ссылки между объектами разных поколений. Чтобы собрать молодое поколение, нужно знать, какие ссылки на него существуют в полях объектов старого поколения (они становятся частью root set-а). Разумеется, сканировать старое поколение целиком значит похоронить всю идею generational GC, объем сканирования нужно как-то сузить. Для этого JVM разбивает кучу на блоки-«карты» размером 512 байт, и для каждого такого блока держит однобайтовый признак «была ли в пределах этого блока запись ссылки». Каждая запись ссылки обновляет заодно и card marks: очень грубо, строчка a.f = ref из java-кода превращается примерно в

a.f = ref;
cardMarks[ (addressOf(a.f)-HEAP_START) >> 9 ] = 1
Т.е. мы записали ссылку в поле какого-то объекта, и пометили блок памяти, содержащий этот объект, как модифицированный. (Это относится только к записям ссылок — примитивные типы card marks не обновляют, потому что GC до них нет дела). Перед сборкой молодого поколения GC пройдет все модифицированные блоки, соберет из них ссылки, ведущие в молодое поколение, и добавит их в root set.

Код card marking такой простой потому, что мы заранее знаем начало кучи, какого она размера, и что она непрерывна. Поэтому нам достаточно одного-единственного массива cardMarks, и мы можем уже на старте JVM аллоцировать его нужного размера (= HEAP_SIZE / 512), сразу подо всю, даже еще не аллоцированную, кучу. Очевидно, что если теперь a в коде выше вдруг указывает на объект на стеке, то мы получим выход за границы массива cardMarks, потому что стеки потоков точно никак не попадут в зарезервированный под кучу интервал адресов. Чтобы обрабатывать ссылки на объекты в куче и объекты на стеке одновременно нужно, чтобы код card marking-а был заметно сложнее, скорее всего, содержал какие-то проверки и условные переходы. А это, на минуточку, код записи ссылки — одна из самых базовых операций, из самых часто исполняемых фрагментов кода. Типичная java-программа ссылками оперирует, пожалуй, чаще, чем примитивными типами! Получается, что немного ускорив аллокацию (точнее, де-аллокацию, сборку мусора — аллокация из TLAB в яве и так быстрее некуда) за счет использования стека мы одновременно замедлили один из самых горячих фрагментов кода — запись ссылки. Какой итоговый эффект это окажет на производительность/время отклика приложения в целом — большой и нетривиальный вопрос.

Card marking это только один из примеров того, как неожиданно непросто впихнуть стековые объекты в архитектуру JVM, годами заточенную под манипуляции объектами из кучи. Этот пример простой, но, возможно, уже не самый актуальный — как я понимаю (могу ошибаться), в G1 уже пришлось делить общий массив cardMarks на отдельные массивчики для каждого блока. Возможно, теперь уже не так уж сложно втиснуть в эту схему еще несколько «блоков» для стеков потоков. Но это не единственный такой пример, если судить по переписке в hotspot-dev:

…I tried implementing direct stack allocation in Hotspot a couple of years ago. It was a pain to try to allocate anything outside the heap — there are a lot of checks to make sure that your objects live on the heap. I ended up creating TLAB-like regions in the heap that could hold objects allocated in a stack-like way. It was a lot easier that way, and seemed to give the kinds of performance benefits you would expect. (Jeremy Manson, 01/27/14, @hotspot-dev)

— оказывается, что проще создать свой собственный «стек» с гетерами и панкратионом: откусывать от общей кучи пулы памяти на каждый поток, и использовать их для аллокации неубегающих объектов, на манер стека. И прототип реализации был написан еще два года назад. И где тот стек сейчас?..

…Я сейчас думаю, что, возможно, дело вообще не в сложности технической реализации аллокации на стеке. Дело в том, что непонятно, так ли уж это нужно. В самом деле, чтобы устранить аллокацию в куче нужно а) чтобы алгоритм EA сумел показать, что объект не утекает за границы текущей единицы компиляции б) чтобы алгоритм скаляризации сумел преобразовать все обращения к этому объекту в обращения к его скаляризованной версии. Переход от скаляризации к аллокации на стеке улучшит пункт «б» (сделает его почти 100%-ным). Сейчас, в некоторых случаях, алгоритм скаляризации может спасовать, даже если алгоритм EA и распознал локальность объекта, а с введением аллокации на стеке почти все такие случаи будут обрабатываться. Но вот много ли таких случаев? Точнее: во-первых, в каком проценте случаев скаляризация пасует сейчас, и во-вторых — какой процент от общего числа аллоцированных в программе объектов мы сможем таким образом выиграть? Я экспериментирую сейчас с различными сценариями, и складывается ощущение, что ответ на первый вопрос может быть довольно заметным — ну, скажем, сейчас мы скаляризуем 60-70% объектов, распознанных EA как неубегающие, а будем аллоцировать на стеке все 100%. А вот общий эффект для среднестатистической программы может быть скромным, если вообще заметным.

Вот недавно мне попалась (спасибо твиттеру) свежая статья, про очередной улучшенный алгоритм EA, в конце каковой статьи приведены результаты применения улучшенного алгоритма к разным бенчмаркам из наборов DeCapo и SPECjbb2005. Результат: в среднем устранено примерно ~15% аллокаций в куче. Это довольно продвинутый алгоритм EA, в паре со «стековой» аллокацией — то есть, приблизительно и ориентировочно, можно взять эти 15% аллокаций за оценку сверху возможностей используемого сейчас алгоритма EA. И переход от используемой сейчас скаляризации к какому-нибудь варианту «стековой» аллокации позволит выиграть какую-нибудь треть от этих 15%.

Каким бы совершенным не был «скаляризатор», его поле деятельности ограничено теми не-убегающими аллокациями, которые ему скормит EA. Алгоритм EA в java почти не менялся со времен 1.6. Да и чисто теоретически: возможности EA отыскать не-убегающие аллокации, в свою очередь, тоже ограничены: в пределах одного метода, даже с учетом агрессивного инлайнинга, особо не развернешься, а межпроцедурную оптимизацию JIT сейчас не выполняет. Увеличивать агрессивность инлайнинга? — Неплохой вариант, в основном потому, что дает пинок, наряду со скаляризацией, сразу целому ряду других оптимизаций. И действительно, в 1.8 многие ограничения на инлайнинг ослаблены, и я вижу, как некоторые сценарии, не скаляризовавшиеся в 1.7, в 1.8 начали скаляризоваться. Но особо далеко по этому пути тоже не пройдешь: чрезмерный инлайнинг раздувает код, и с какого-то момента это начинает уже ухудшать производительность. Получается, что существенный прирост можно получить только совершенствуя сразу и скаляризацию, и алгоритм EA, и, по-возможности, увеличивая размер единицы оптимизации, либо подключая межпроцедурную оптимизацию.

В таком рассуждении есть нюанс: когда мы пытаемся оценить профит от улучшений, прогоняя их на существующих уже программах, мы незаметно попадаем в ловушку. Существующие программы написаны с использованием существующих практик написания кода, и в этих практиках — и вообще в опыте разработчиков — уже учтены характерные особенности языка и платформы. Опытные разработчики, сознательно или бессознательно, уже оптимизируют свой код под известные сильные и слабые стороны платформы. Говоря проще: если бы было общеизвестно, что в java есть великолепный EA и скаляризатор, и что редкий временный объект имеет шанс быть аллоцированным в куче — многие программы были бы написаны сильно иначе, чем они написаны сейчас, когда общеизвестно, что да, GC довольно неплохо управляется с короткоживущими объектами, а некоторые объекты даже и скаляризуются, но не всегда, и не все, и вообще как фишка ляжет. В упомянутой выше статье, наряду с набором java-бенчмарков, был взят аналогичный набор Scala-бенчмарков (ScalaDeCapo). И разница очень заметна: (Java)DeCapo от включения EA получает бонус в -5% аллокаций, а ScalaDeCapo — в -15%. Общий прирост производительности (Java)DeCapo: +2.2%, ScalaDeCapo: +9%. В Scala другие наборы best practices, другой компилятор, другая стандартная библиотека…

Stack and heap. Структуры данных в .NET

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

Разделение памяти

По умолчанию, как только .NET приложение запускается и определяется виртуальный адрес текущего процесса, создаются следующие «кучи»:

Куча для кода — JIT-компилируемый нативный код

Малая объектная куча — объекты до 85 кб

Большая объектная куча — объекты свыше 85 кб*

Куча для обработки данных

*примечание: в случае массивов для данных типа double существует исключение, согласно которому они хранятся в большой объектной куче задолго до достижения размера в 85 кб (double[] считается системой  «большим» объектом при достижении размера в 1000 элементов). По отношению к оптимизации 32-битного кода это, конечно, не очень хорошо.

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

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

Впрочем, куча — это не единственная структура данных, которой может похвалиться вселенная .NET. К примеру, есть еще и стек, который крайне полезен для хранения «специфических» типов данных. Сейчас мы рассмотрим в деталях, как устроены эти структуры данных в деталях.

 

Стек

Стек — это структура данных, организованная по принципу LIFO (последний вошел — первый вышел). Если вдуматься, это идеальное решение для хранения данных, к которым вскоре предстоит обратиться (легко извлекаются с вершины стека). Де-факто природа области стека заключается в двух постулатах: «помнить» порядок выполнения и хранить значимые типы данных.

 

Запоминание порядка выполнения — обращение к стеку

Большая часть кода, который мы пишем, инкапсулирован в классы и методы, которые вызывают другие методы, и так далее. .NET Framework обязан всегда «помнить» порядок вызовов участков кода. Более того, так же нужно хранить данные о состоянии переменных и значениях параметров, передаваемых при вызове методов (дабы суметь восстановить состояние вызывающего метода после завершения работы вызываемого).

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

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

Давайте взглянем на следующий участок кода:

Дабы вызвать Method2, фреймворк должен сохранить адрес конца выполнения метода, которым будет следующая строчка кода (строчка 4 в примере ниже). Этот адрес вместе с параметрами и локальными переменными вызываемого и вызывающего метода хранятся в стеке вызова, как показано на схеме ниже.

Также вы можете увидеть, что происходит, когда Method3 завершает свое выполнение (стек-фрейм покидает стек вызова).

 

Хранение значимых типов

Также стек используется для хранения переменных любых значимых типов .NET — включая: bool, decimal, int и так далее.

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

Видео курсы по схожей тематике:

 

Куча

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

 

Хранение ссылочных типов

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

Рассмотрим следующий код:

Фигура ниже иллюстрирует, как выглядит стек и куча в плане хранения данных:

OBJREF, хранимый в стеке, на самом деле является ссылкой на объект MyClass, хранимый в куче.

Заметка: выражение MyClass myObj совершенно не занимает места в куче переменной myObj. Здесь всего лишь создается переменная OBJREF в стеке, после чего она инициализируется значением null. Как только выполняется команда new, куча получает действительное место памяти объекта, а сам ссылочный объект получает по адресу свое значение.

 

Значимые типы против ссылочных типов (стек против кучи)

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

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

Бесплатные вебинары по схожей тематике:

Конечно, хранение одного вида информации в стеке, другого в куче, имеет свои причины, которые мы рассмотрим в грядущих статьях. 🙂

 

Заключение

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

До новых встреч!

Автор перевода: Евгений Лукашук

Источник

Управление памятью Java для виртуальной машины Java (JVM)

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

Приготовьтесь к глубокому погружению!

В этой статье мы обсудим виртуальную машину Java (JVM), понимаем управление памятью, инструменты мониторинга памяти, мониторинг использования памяти и действия по сборке мусора (GC).

Как вы увидите, для настоящей оптимизации существует множество различных моделей, методов, инструментов и советов.

Виртуальная машина Java (JVM)

JVM — это абстрактная вычислительная машина, которая позволяет компьютеру запускать программу Java. Существует три понятия JVM: спецификация (где указана работа JVM. Но реализация была предоставлена ​​Sun и другими компаниями), реализация (известная как (JRE) Java Runtime Environment) и экземпляр (после написание команды Java, для запуска класса Java создается экземпляр JVM).

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

Структура памяти Java (JVM) Память

JVM разделена на несколько частей: куча памяти, не куча памяти и прочее.

Рис. 1.1 источник https://www.yourkit.com/docs/kb/sizes.jsp

Куча памяти

Память кучи — это область данных времени выполнения, из которой выделяется память для всех экземпляров класса Java и массивов. Куча создается при запуске виртуальной машины Java и может увеличиваться или уменьшаться в размере во время работы приложения. Размер кучи можно указать с помощью параметра –Xms VM. Куча может иметь фиксированный или переменный размер в зависимости от стратегии сборки мусора. Максимальный размер кучи можно установить с помощью параметра –Xmx.По умолчанию максимальный размер кучи равен 64 МБ.

Память без кучи

Виртуальная машина Java имеет память, отличную от кучи, называемую памятью без кучи. Он создается при запуске JVM и хранит структуры для каждого класса, такие как пул констант времени выполнения, данные полей и методов, а также код для методов и конструкторов, а также интернированные строки. Максимальный размер памяти без кучи по умолчанию составляет 64 МБ. Это можно изменить с помощью опции –XX:MaxPermSize VM.

Другая память

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

Структура динамической памяти Java (JVM)

Источник рис. 1.2 http://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java

Куча JVM физически разделена на две части (или поколения): питомник (или молодое пространство/молодое поколение ) и старое пространство (или старое поколение ).

Питомник — это часть кучи, зарезервированная для размещения новых объектов. Когда питомник заполняется, сбор мусора осуществляется путем запуска специальной молодой коллекции , где все объекты, прожившие достаточно долго в питомнике, перемещаются (перемещаются) в старое пространство, тем самым освобождая питомник для размещения большего количества объектов.Эта сборка мусора называется Minor GC . Детская разделена на три части — Память Эдема и два пространства Память выжившего .

Важные моменты о детской комнате:

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

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

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

Модели памяти Java

Постоянное поколение (заменено Metaspace начиная с Java 8)

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

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

В Java 8 нет Perm Gen, что означает, что больше нет проблем с пространством «java.lang.OutOfMemoryError: PermGen». В отличие от Perm Gen, который находится в куче Java, Metaspace не является частью кучи. Большинство распределений метаданных класса теперь выделяется из собственной памяти.Metaspace по умолчанию автоматически увеличивает свой размер (вплоть до того, что обеспечивает базовая ОС), в то время как Perm Gen всегда имеет фиксированный максимальный размер. Для установки размера метапространства можно использовать два новых флага: «-XX:MetaspaceSize » и «-XX:MaxMetaspaceSize ». Основная идея Metaspace заключается в том, что время жизни классов и их метаданных соответствует времени жизни загрузчиков классов. То есть, пока жив загрузчик классов, метаданные остаются живыми в метапространстве и не могут быть освобождены.

Кэш кода

Когда программа Java запускается, она выполняет код многоуровнево. На первом уровне он использует клиентский компилятор (компилятор C1) для компиляции кода с инструментами. Данные профилирования используются на втором уровне (компилятор C2) для серверного компилятора для оптимизации этого кода. Многоуровневая компиляция не включена по умолчанию в Java 7, но включена в Java 8.

Компилятор Just-In-Time (JIT) сохраняет скомпилированный код в области, называемой кэшем кода.Это специальная куча, в которой хранится скомпилированный код. Эта область очищается, если ее размер превышает пороговое значение, и эти объекты не перемещаются GC.

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

Область метода

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

Пул памяти

Пулы памяти создаются диспетчерами памяти JVM для создания пула неизменяемых объектов. Пул памяти может принадлежать Heap или Perm Gen, в зависимости от реализации диспетчера памяти JVM.

Пул констант времени выполнения

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

Память стека Java

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

Переключатели кучи памяти Java

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

. .
Виртуальный переключатель Описание коммутатора VM
Хмс Для установки начального размера кучи при запуске JVM
-Хмх Для установки максимального размера кучи
-Хмн Для установки размера молодого поколения, остальное место для старого поколения
-XX:PermGen Для установки начального размера постоянной памяти поколения
-ХХ:МаксПермГен Для установки максимального размера Perm Gen
-XX:Коэффициент выживания Для обеспечения соотношения пространства Eden, например, если размер молодого поколения составляет 10 м, а переключатель виртуальной машины -XX:SurvivorRatio=2, тогда 5 м будет зарезервировано для пространства Eden и 2.5 м каждая для обеих ячеек Survivor. Значение по умолчанию: 8
-XX:Новое соотношение Для обеспечения соотношения размеров старого/нового поколения. Значение по умолчанию 2

Таблица 1.1

Сбор мусора

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

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

Маркировка и подметание Модель сбора мусора

JVM использует модель сборки мусора пометки и очистки для выполнения сборки мусора всей кучи. Сборка мусора пометки и очистки состоит из двух фаз: фазы маркировки и фазы очистки.

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

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

Типы сборки мусора Java

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

Serial GC (-XX:+UseSerialGC) : Serial GC использует простой подход mark-sweep-compact для сборки мусора молодого и старого поколения, то есть Minor и Major GC

Чтобы включить Serial Collector, используйте:

-XX:+UseSerialGC

Параллельный сборщик мусора (-XX:+UseParallelGC) : Параллельный сборщик мусора аналогичен последовательному сборщику мусора, за исключением того, что он порождает N потоков для сборки мусора молодого поколения, где N — количество ядер ЦП в системе. Мы можем контролировать количество потоков, используя параметр JVM –XX:ParallelGCThreads=n .Это сборщик JVM по умолчанию в JDK 8

.

Чтобы включить параллельный сборщик мусора, используйте:

-XX:+UseParallelGC

Параллельный старый сборщик мусора (-XX:+UseParallelOldGC) : Это то же самое, что и параллельный сборщик мусора, за исключением того, что он использует несколько потоков для сборки мусора как молодого, так и старого поколения

Чтобы включить Parallel OLDGC, используйте:

-XX:+UseParallelOldGC

Сборщик параллельных меток (CMS) (-XX:+UseConcMarkSweepGC) : CMS также называется параллельным сборщиком с низкой паузой.Он выполняет сборку мусора для старого поколения. Сборщик CMS пытается свести к минимуму паузы, связанные со сборкой мусора, выполняя большую часть работы по сборке мусора одновременно в потоках приложения. Сборщик CMS на молодом поколении использует тот же алгоритм, что и у параллельного сборщика. Этот сборщик мусора подходит для отзывчивых приложений, где мы не можем позволить себе более длительные паузы. Мы можем ограничить количество потоков в сборщике CMS, используя параметр –XX:ParallelCMSThreads=n JVM

.

Чтобы включить CMS Collector, используйте:

-XX:+UseConcMarkSweepGC

Сборщик мусора G1 (-XX:+UseG1GC) : Сборщик мусора G1 доступен в Java 7, и его долгосрочная цель — заменить сборщик CMS.Сборщик G1 — это параллельный, параллельный и постепенно компактный сборщик мусора с малой паузой. Первый сборщик мусора работает не так, как другие сборщики, и здесь нет концепции пространства для молодых и старых поколений. Он делит пространство кучи на несколько областей кучи одинакового размера. Когда вызывается сборщик мусора, он сначала собирает область с меньшим количеством оперативных данных, следовательно, «сначала мусор».

Для включения коллектора G1 используйте:

-XX:+UseG1GC

G1 планируется в качестве долгосрочной замены Concurrent Mark-Sweep Collector (CMS).При сравнении G1 с CMS есть отличия, которые делают G1 лучшим решением. Одно отличие состоит в том, что G1 является уплотняющим коллектором. G1 достаточно компактен, чтобы полностью избежать использования мелких списков свободных мест для распределения и вместо этого полагается на регионы. Это значительно упрощает части сборщика и в основном устраняет потенциальные проблемы фрагментации. Кроме того, G1 предлагает более предсказуемые паузы для сборки мусора, чем сборщик CMS, и позволяет пользователям указывать желаемые цели паузы.

В Java 8 сборщик G1 поставляется с удивительной оптимизацией, известной как String Deduplication .Это позволяет сборщику мусора идентифицировать строки, которые имеют несколько вхождений в куче, и изменять их, чтобы они указывали на один и тот же внутренний массив char[], чтобы в куче не было нескольких копий. Это можно включить с помощью JVM -XX:+UseStringDeduplication. аргумент.

G1 — это сборщик мусора по умолчанию в JDK 9.

Пример использования

Более 50% кучи Java занято оперативными данными.

Скорость выделения или продвижения объекта значительно различается.

Нежелательные длительные паузы при сборке мусора или уплотнении (дольше от 0,5 до 1 секунды)

Мониторинг использования памяти и активности GC

Нехватка памяти часто является причиной нестабильности и зависания приложений Java. Следовательно, нам необходимо отслеживать влияние сборки мусора на время отклика и использование памяти, чтобы обеспечить как стабильность, так и производительность. Однако мониторинга использования памяти и времени сборки мусора недостаточно, поскольку сами по себе эти два элемента не сообщают нам, влияет ли сборка мусора на время отклика приложения.Только приостановка сборщика мусора напрямую влияет на время отклика, и сборщик мусора также может выполняться одновременно с приложением. Поэтому нам необходимо соотнести приостановки, вызванные сборкой мусора, со временем отклика приложения. Исходя из этого нам необходимо отслеживать следующее:

  • Использование разных пулов памяти (Eden, Survivor и старое поколение). Нехватка памяти — главная причина повышенной активности GC
  • Если общее использование памяти постоянно увеличивается, несмотря на сборку мусора, имеет место утечка памяти, которая неизбежно приведет к нехватке памяти .В этом случае необходим анализ кучи памяти
  • Количество коллекций молодого поколения предоставляет информацию о коэффициенте оттока (скорости размещения объектов). Чем выше число, тем больше объектов выделяется. Большое количество молодых коллекций может быть причиной проблемы времени отклика и старения поколения (поскольку молодое поколение больше не может справиться с количеством объектов)
  • Если загрузка старого поколения сильно колеблется без повышения после GC, то объекты без необходимости копируются из молодого поколения в старое.Для этого есть три возможные причины: молодое поколение слишком мало, высокая скорость оттока или слишком много транзакционной памяти используется
  • .
  • Высокая активность сборщика мусора обычно отрицательно влияет на загрузку ЦП. Однако прямое влияние на время отклика оказывают только приостановки (события остановки мира). Вопреки распространенному мнению, приостановка не ограничивается основными общими сборами. Поэтому важно отслеживать приостановки в зависимости от времени отклика приложения
  • .
jstat

Утилита jstat использует встроенный инструментарий Java HotSpot VM для предоставления информации о производительности и потреблении ресурсов запущенными приложениями.Инструмент можно использовать при диагностике проблем с производительностью и, в частности, проблем, связанных с размером кучи и сборкой мусора. Утилита jstat не требует запуска ВМ с какими-либо специальными параметрами. Встроенное инструментирование в виртуальной машине Java HotSpot включено по умолчанию. Эта утилита включена в загрузку JDK для всех операционных систем. Утилита jstat использует идентификатор виртуальной машины (VMID) для идентификации целевого процесса.

Использование команды jstat с параметром gc для определения использования памяти кучи JVM.

/bin/jstat –gc

Рис. 1.3

СОЦ Текущая емкость оставшегося пространства 0 (КБ)
С1С Текущая емкость оставшегося пространства 1 (КБ)
С0У Использование оставшегося пространства 0 (КБ)
С1У Использование выжившего места 1 (КБ)
ЕС Текущая емкость пространства Эдема (КБ)
ЕС Использование пространства Eden (КБ)
ОС Текущая емкость старого пространства (КБ)
ОУ Старое использование пространства (КБ)
МС Емкость метасапа (КБ)
МУ Использование метапространства (КБ)
CCSC Емкость пространства сжатого класса (КБ)
CCSU Используемое пространство сжатого класса (КБ)
ЮГК Количество мероприятий по сбору мусора молодым поколением
ЮГКТ Время сбора мусора молодого поколения
ФСК Количество полных событий GC
ФГКТ Время полной сборки мусора
GCT Общее время сборки мусора

Таблица 1.2

jmap

Утилита jmap выводит статистику, связанную с памятью, для работающей виртуальной машины или файла ядра. В JDK 8 представлены Java Mission Control, Java Flight Recorder и утилита jcmd для диагностики проблем с приложениями JVM и Java. Рекомендуется использовать новейшую утилиту jcmd вместо утилиты jmap для расширенной диагностики и снижения производительности.

Параметр –heap можно использовать для получения следующей информации о куче Java:

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

/bin/jmap – куча

Рис. 1.4

джкмд

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

Дамп кучи (дамп hprof) можно создать с помощью следующей команды:

jcmd Сборщик мусора.heap_dump имя_файла=<ФАЙЛ>

Приведенная выше команда аналогична использованию

.

jmap –dump:file=

Но рекомендуется использовать jcmd .

джхат

Инструмент jhat предоставляет удобные средства для просмотра топологии объектов в моментальном снимке кучи. Этот инструмент заменяет инструмент анализа кучи (HAT). Инструмент анализирует дамп кучи в двоичном формате (например, дамп кучи, созданный jcmd ).Эта утилита может помочь отладить непреднамеренное отношение объектов . Этот термин используется для описания объекта, который больше не нужен, но остается живым благодаря ссылкам по какому-либо пути из корневого набора. Это может произойти, например, если непреднамеренная статическая ссылка на объект остается после того, как объект больше не нужен, если наблюдатель или слушатель не может отменить регистрацию в своем субъекте, когда он больше не нужен, или если поток, ссылающийся на объект не завершается, когда должен.Непреднамеренное объектное отношение — это эквивалент утечки памяти в языке Java.

Мы можем проанализировать дамп кучи, используя jhat со следующей командой

jhat

Эта команда читает файл .hprof и запускает сервер на порту 7000.

Рис. 1.5

Когда мы подключаемся к серверу с помощью http://localhost:7000, мы можем выполнить стандартный запрос или создать язык объектных запросов (OQL). Запрос All Classes отображается по умолчанию.На этой странице по умолчанию отображаются все классы, присутствующие в куче, за исключением классов платформы. Этот список отсортирован по полному имени класса и разбит по пакетам. Щелкните имя класса, чтобы перейти к запросу класса. Второй вариант этого запроса включает классы платформы. Классы платформы включают классы, полные имена которых начинаются с таких префиксов, как java, sun или javax.swing. С другой стороны, запрос класса отображает информацию о классе. Это включает в себя его суперкласс, любые подклассы, элементы данных экземпляра и члены статических данных.На этой странице можно перейти к любому из классов, на которые имеются ссылки, или перейти к запросу экземпляра. Запрос экземпляра отображает все экземпляры данного класса.

Высокопрофильный

HPROF — это инструмент для профилирования кучи и ЦП, поставляемый с каждым выпуском JDK. Это библиотека динамической компоновки (DLL), которая взаимодействует с JVM с помощью интерфейса Java Virtual Machine Tool (JVMTI). Инструмент записывает информацию о профилировании либо в файл, либо в сокет в формате ASCII или двоичном формате.Инструмент HPROF может отображать использование ЦП, статистику распределения кучи и отслеживать профили конфликтов. Кроме того, он может сообщать о полных дампах кучи и состояниях всех мониторов и потоков в виртуальной машине Java (JVM). Что касается диагностики проблем, HPROF полезен при анализе производительности, конфликтов блокировок, утечек памяти и других проблем.

Мы можем вызвать инструмент HPROF, используя:

Java –agentlib:hprof ToBeProfiledClass

Java –agentlib:hprof=heap=sites ToBeProfiledClass

В зависимости от типа запрошенного профилирования HPROF инструктирует JVM отправлять его на соответствующие события.Затем инструмент обрабатывает данные о событиях в профилирующую информацию. По умолчанию информация о профилировании кучи записывается в файл java.hprof.txt (в формате ASCII) в текущем рабочем каталоге.

Следующая команда

javac –J-agentlib:hprof=heap=sites Hello.java

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

Аналогично, дамп кучи можно получить с помощью опции heap=dump .Вывод

javac –J-agentlib:hprof=heap=dump Hello.java

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

Инструмент HPROF может собирать информацию об использовании ЦП путем выборки потоков.

Для получения результатов профиля выборки использования ЦП можно использовать следующую команду:

javac –J-agentlib:hprof=cpu=samples Hello.java

Агент HPROF периодически производит выборку стека всех запущенных потоков для записи наиболее частых активных трассировок стека.

Существуют и другие инструменты, такие как VisualVM, который предоставляет нам подробную информацию об использовании памяти, сборке мусора, дампах кучи, профилировании ЦП и памяти и т. д. в виде графического интерфейса.

ВизуалВМ

VisualVM — это инструмент, основанный на платформе NetBeans, и его архитектура имеет модульную структуру, что означает, что его легко расширять с помощью подключаемых модулей. VisualVM позволяет нам получать подробную информацию о приложениях Java, пока они работают на JVM, и это может быть в локальной или удаленной системе.Сгенерированные данные могут быть извлечены с помощью инструментов Java Development Kit (JDK), а все данные и информация о нескольких приложениях Java могут быть быстро просмотрены как для локальных, так и для удаленных приложений. Также можно сохранять и собирать данные о программном обеспечении JVM виртуальной машины Java и сохранять данные в локальной системе. VisualVM может выполнять выборку ЦП, выборку памяти, запускать сборку мусора, анализировать ошибки кучи, делать моментальные снимки и многое другое.

Включение портов JMX

Мы можем включить удаленные порты JMX, добавив следующие системные свойства при запуске приложения Java:

  • -Дком.sun.management.jmxremote
  • -Dcom.sun.management.jmxremote.port=<Порт>
  • -Dcom.sun.management.jmxremote.

Теперь мы можем использовать VisualVM для подключения к удаленному компьютеру и просмотра использования ЦП, выборки памяти, потоков и т. д. Мы также можем создавать дампы потоков и дампы памяти на удаленном компьютере при подключении через удаленный порт JMX.

На рис. 1.6 показан список приложений, работающих в локальной и удаленной системах. Чтобы подключиться к удаленной системе, щелкните правой кнопкой мыши «Удаленный» и добавьте имя хоста, а в разделе «Дополнительные параметры» укажите порт, который использовался при запуске приложения на удаленном компьютере.Если в локальном или удаленном разделе перечислены приложения, дважды щелкните их, чтобы просмотреть сведения о приложении.

 

 

Рис. 1.6

Для сведений о приложении есть четыре вкладки: Обзор, Монитор, Потоки и Сэмплер.

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

Интересная вкладка — это вкладка Monitor . На этой вкладке отображается использование ЦП и памяти приложением. В этом представлении четыре графика.

 

Рис. 1.7

На первом графике показано использование ЦП и использование ЦП сборщиком мусора. По оси X показана временная метка в зависимости от процента использования.

Второй график в правом верхнем углу отображает пространство кучи и пространство Perm Gen или Metaspace. Он также показывает максимальный размер кучи памяти, сколько используется приложением и сколько доступно для использования.Этот график особенно полезен при анализе приложений, в которых возникают ошибки java.lang.OutOfMemoryError: Java heap space . Когда приложение выполняет работу, интенсивно использующую память, используемая куча (обозначенная синим цветом на графике) всегда должна быть меньше размера кучи (обозначенной оранжевым цветом на графике). Когда используемая куча почти совпадает с размером кучи или когда системе больше не хватает места для выделения/расширения размера кучи, а используемая куча продолжает увеличиваться, мы можем ожидать ошибку кучи.Более подробную информацию о куче можно получить, взяв «Дамп кучи». Когда возникает ошибка нехватки памяти, дамп кучи можно получить, добавив текущие параметры ВМ:

-XX:+HeapDumpOnOutOfMemoryError –XX:HeapDumpPath=[путь к файлу]

Это позволит создать файл .hprof по указанному пути.

Рис. 1.8

На рис. 1.8 показан дамп кучи для одного из приложений. На вкладке сводки отображается основная информация, такая как общее количество классов, общее количество экземпляров, загрузчики классов, корни GC и сведения о среде, в которой работает приложение.Анализ на рис. 1.8 показывает, какие типы объектов выделяются чаще всего и где это происходит. Большие объекты создают множество других объектов в своем конструкторе или имеют множество полей. Мы также должны проанализировать области кода, которые, как известно, массово параллелизуются в производственных условиях. Под нагрузкой эти места не только будут выделять больше, но и увеличат синхронизацию внутри самого управления памятью. Высокое использование памяти является причиной чрезмерной сборки мусора.В некоторых случаях аппаратные ограничения делают невозможным простое увеличение размера кучи JVM. В других случаях увеличение размера кучи не решает, а только отсрочивает проблему, потому что использование просто продолжает расти. Возможны следующие анализы с использованием дампов кучи для выявления утечек памяти и выявления пожирателей памяти.

Каждый объект, который больше не нужен, но остается ссылкой приложения, может считаться утечкой памяти. Практически нас интересуют только те утечки памяти, которые растут или занимают много памяти.Типичная утечка памяти — это утечка, при которой указанный тип объекта создается повторно, но не удаляется сборщиком мусора. Чтобы идентифицировать этот тип объекта, необходимы несколько дампов кучи, которые можно сравнить с помощью дампов трендов. Каждое приложение Java имеет большое количество String , char[] и других стандартных объектов Java. На самом деле String и char[] обычно имеют наибольшее количество экземпляров, но их анализ ни к чему нас не приведет. Даже если бы у нас была утечка объектов String, это, скорее всего, произошло бы потому, что на них ссылается объект приложения, который представляет основную причину утечки.Следовательно, концентрация на классах нашего приложения даст более быстрые результаты.

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

  • Анализ тенденций не привел нас к утечке памяти
  • Наше приложение использует слишком много памяти, но не имеет очевидной утечки памяти, и нам нужно оптимизировать код
  • Не удалось провести анализ тенденций, поскольку объем памяти растет слишком быстро, а JVM дает сбой

Во всех трех случаях основной причиной, скорее всего, является один или несколько объектов, находящихся в корне большого дерева объектов.Эти объекты предотвращают сбор мусора множества других объектов в дереве. В случае ошибки нехватки памяти вполне вероятно, что несколько объектов препятствуют освобождению большого количества объектов, что приводит к возникновению ошибки нехватки памяти. Размер кучи часто является большой проблемой для анализа памяти. Для создания дампа кучи требуется сама память. Если размер кучи находится на пределе того, что доступно или возможно (32-разрядные JVM не могут выделить более 3,5 ГБ), виртуальная машина Java (JVM) может не создать ее.Кроме того, дамп кучи приостановит работу JVM. Ручной поиск одного объекта, который предотвращает сборку мусора всего дерева объектов, быстро становится пресловутой иголкой в ​​стоге сена.

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

 

Рис. 1.9

Возвращаясь к графикам, доступным (Рис. 1.7) для приложения на вкладке «Монитор», это график классов, расположенный внизу слева. На этом графике отображается общее количество классов, загруженных в приложение, а на последнем графике отображается количество запущенных в данный момент потоков. С помощью этих графиков мы можем увидеть, не использует ли наше приложение слишком много ресурсов ЦП или памяти.

Третья вкладка — это вкладка Threads  .

Рис. 1.10

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

Четвертая вкладка — это вкладка Sampler .Когда мы открываем эту вкладку изначально, она не содержит никакой информации. Мы должны начать один вид выборки/профилирования, прежде чем увидеть информацию. Мы начнем с выборки ЦП. После нажатия кнопки «ЦП» результаты выборки ЦП отображаются в таблице.

Рис. 1.11

Из рис. 1.11 видно, что метод doRun() занимает 54,8% процессорного времени. Мы также видим, что getNextEvent() и readAvailableBlocking() — это два следующих метода, которые потребляют больше процессорного времени.

Следующая выборка — выборка из памяти. Приложение будет заморожено во время выборки до тех пор, пока не будут получены результаты. Из рис. 1.12 мы можем сделать вывод, что приложение хранит массивы Object , int, и char .

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

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

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

Если мы сталкиваемся с ошибкой java.lang.OutOfMemoryError: PermGen space , попробуйте отслеживать и увеличивать объем памяти PermGen с помощью параметров –XX:PermGen и –XX:MaxPermGen JVM.Мы не видим эту ошибку в случае с Java 8 и выше. Если мы видим много полных операций GC, то нам следует попробовать увеличить объем памяти старого поколения. В целом, настройка сборки мусора требует много усилий и времени, и для этого нет жесткого и быстрого правила. Нам нужно попробовать разные варианты и сравнить их, чтобы найти лучший, подходящий для нашего приложения.

Некоторые решения для повышения производительности:

  • Выборка/профилирование прикладного программного обеспечения
  • Настройка сервера и JVM
  • Правильное оборудование и ОС
  • Улучшение кода в соответствии с поведением приложения и результатами выборки (легче сказать, чем сделать!)
  • Правильно используйте виртуальную машину Java (имея оптимальные параметры JVM)
  • -XX:+UseParallelGC при наличии мультипроцессоров

Еще несколько полезных советов:

  • Если у нас нет проблем с паузами, попробуйте предоставить JVM как можно больше памяти
  • Установка –Xms и –Xmx на одно и то же значение
  • Обязательно увеличивайте объем памяти по мере увеличения количества процессоров, так как выделения могут быть распараллелены
  • Не забудьте настроить Perm Gen
  • Свести к минимуму использование синхронизаций
  • Используйте многопоточность, если это выгодно, и помните о накладных расходах нитей.Кроме того, убедитесь, что он работает одинаково в разных средах
  • .
  • Избегайте преждевременного создания объектов. Создание должно быть близко к фактическому месту использования. Это основная концепция, которую мы склонны упускать из виду
  • .
  • JSP обычно медленнее сервлетов
  • StringBuilder вместо объединения строк
  • Используйте примитивы и избегайте объектов. (длинный вместо длинного)
  • По возможности повторно используйте объекты и избегайте создания ненужных объектов
  • equals() дорого стоит, если мы проверяем пустую строку.Вместо этого используйте свойство длины.
  • «==» быстрее, чем equals()
  • n += 5 быстрее, чем n = n + 5. В первом случае генерируется меньше байт-кодов
  • Периодически сбрасывать и очищать сеансы гибернации
  • Массовое обновление и удаление

Создание журналов для GC

Журнал сборщика мусора, или gc.log, представляет собой текстовый файл, в котором хранятся все события очистки памяти JVM: MinorGC, MajorGC и FullGC.

Запустите JVM с указанными ниже параметрами, чтобы создать gc.журнал:

до Java 8

XX:+PrintGCDetails -Xloggc:/app/tmp/myapp-gc.log

из Явы 9

Xlog:gc*:file=/app/tmp/myapp-gc.log

Источники

https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/garbage_collect.html

https://en.wikipedia.org/wiki/Java_virtual_machine

https://www.javatpoint.com/internal-details-of-jvm

https://dzone.com/articles/java-настройка производительности

Java (JVM) Memory Model – Memory Management in Java

https://www.yourkit.com/docs/kb/sizes.jsp

https://www.infoq.com/articles/Java-PERMGEN-Removed

http://blog.andrestingress.com/2016/10/19/java-codecache

https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/toc.html

https://javaperformance.wordpress.com/2017/02/05/java-heap-memory-usage-using-jmap-and-jstat-command/

Мы надеемся, что эта статья (Управление памятью Java для виртуальной машины Java) оказалась полезной.Если есть что-то, что вы хотели бы добавить, дайте нам знать в комментариях ниже!

Обязательно ознакомьтесь с другим нашим блогом: DevOps с использованием Jenkins, Docker и Kubernetes, Начало работы с Sikuli для автоматизации тестирования, Автоматизация, связанная с DevOps, и многое другое здесь.

Авторы этой статьи Акшай А. и Абхиджит Аркалгуд.

Родственные

Что такое пространство кучи Java? Узнайте о размере кучи в Java

Куча Java — это область памяти, используемая для хранения объектов, созданных приложениями, работающими на JVM. При запуске JVM создается память кучи, и любые объекты в куче могут совместно использоваться потоками, пока приложение работает. Размер кучи может варьироваться, поэтому многие пользователи ограничивают размер кучи Java до 2-8 ГБ, чтобы свести к минимуму паузы при сборке мусора.

Разница между кучей и стеком

Хотя куча является важной частью процесса управления памятью, она работает вместе со стеком для выполнения программы. По сравнению с кучей стек используется только для выполнения одного потока, а не является глобально доступным и общим для всех потоков.Память стека использует структуру LIFO (последний пришел — первый ушел), где каждый стек содержит значения, специфичные для метода. Когда метод заканчивается, блок стирается и становится доступным для следующего метода. С другой стороны, куча не следует порядку и динамически управляет блоками памяти.

Типы приложений, для которых важно пространство кучи

  • Вычисления в памяти
  • Базы данных NoSQL
  • Приложения для работы с большими данными
  • Аналитика
  • Веб-персонализация
  • Электронная коммерция

Увеличение размера кучи Java с помощью платформы Azul1 Prime®3

JVM могут легко использовать кучу 100 ГБ, но время паузы для GC может длиться много минут.Это ограничивает производительность и масштабируемость приложений и не позволяет приложениям Java использовать все ресурсы современных обычных серверов. Очень большие размеры кучи часто очень практичны, если вы можете устранить связанные с этим проблемы с производительностью. Azul Platform Prime® (ранее Azul Zing) — первая виртуальная машина JVM, решающая проблему пауз сборщика мусора и позволяющая увеличить размер кучи до 8 ТБ без потери производительности.

Увеличенная куча памяти Java

  • Не требует разделения рабочих данных между несколькими экземплярами JVM
  • Позволяет создавать больше объектов
  • Заполняет больше времени
  • Позволяет приложению работать дольше между событиями сборки мусора (GC)

Меньший объем памяти Java куча

  • Содержит меньше объектов
  • Заполняется быстрее
  • Мусор собирается чаще (но паузы короче)
  • Может привести к ошибкам нехватки памяти

Достаточно ли 2–8 ГБ кучи памяти для большинства Java Приложения?

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

  • Распространенное использование «поперечного масштабирования» внутри машин
  • Использование «внешней» памяти с растущими наборами данных (большие базы данных и использование внешних кэшей данных, таких как memcached, JCache и JavaSpaces)
  • Непрерывная работа над бесконечной проблемой распределения

Проблема в программном стеке, который налагает искусственные ограничения на память для каждого экземпляра.Время паузы сборки мусора — единственный фактор, ограничивающий размер экземпляра, и, как мы убедились на практике, даже обширная настройка сборки мусора (GC) не избавляет от этого. Как только вы решили GC, вы решили проблему. Инновационный алгоритм сборки мусора Azul C4 полностью параллелен, устраняя влияние очень больших куч на производительность.

2 решение java.lang.OutOfMemoryError в Java

Каждый в Java-разработке время от времени сталкивается с java.lang.OutOfMemoryError . OutOfMemoryError в Java — это одна из проблем, которая больше связана с системным ограничением (памятью), а не с ошибками программирования в большинстве случаев, хотя в некоторых случаях у вас может возникнуть утечка памяти , которая вызывает OutOfMemoryError .Я обнаружил, что хотя java.lang.OutOfMemoryError является довольно распространенным базовым знанием ее причины, а решение в значительной степени неизвестно младшим разработчикам. Книги для начинающих, такие как Head First Java, не учат многому тому, как справляться с такого рода ошибками. Вам нужен реальный опыт работы с производственными системами, обработки большого количества пользовательских сеансов для устранения неполадок и устранения проблем с производительностью, таких как нехватка памяти.

Если вы хотите хорошо разбираться в устранении неполадок и анализе производительности, вам необходимо изучить несколько книг по производительности Java и профилированию e.грамм. Производительность Java Полное руководство Скотта Оукса или производительность Java Бину Джона. Они являются отличным ресурсом для старших разработчиков Java, а также обучают вас инструментам и процессам для обработки таких ошибок, как java.lang.OutOfMemoryError.

В этой статье мы рассмотрим что такое java.lang.OutOfMemoryError ; Почему OutOfMemoryError появляется в Java-приложении, другой тип OutOfMemoryError и . Как исправить OutOfMemoryError в Java .

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

И, если вы серьезно относитесь к совершенствованию своих продвинутых навыков работы с JVM и изучаете такие вещи, как получение и анализ дампов кучи, настоятельно рекомендуем вам присоединиться к курсу «Производительность Java-приложений и управление памятью » на Udemy. Это один из продвинутых курсов для Java-программистов, позволяющий узнать больше об управлении производительностью и памятью, включая устранение утечек памяти в Java.

Что такое java.lang.OutOfMemoryError в Java OutOfMemoryError в Java является подклассом класса java.lang.VirtualMachineError , и JVM выдает java.lang.OutOfMemoryError, когда не хватает памяти в куче . OutOfMemoryError в Java может появиться в куче в любое время, в основном, когда вы пытаетесь создать объект, а в куче недостаточно места для размещения этого объекта. Однако Javadoc OutOfMemoryError не очень информативен по этому поводу.

Типы OutOfMemoryError в Java Я видел в основном два типа OutOfMemoryError в Java:

1) java.lang.OutOfMemoryError: пространство кучи Java
2) java.lang.OutOfMemoryError: пространство PermGen

Хотя оба они возникают из-за запуска JVM вне памяти они сильно отличаются друг от друга, и их решения независимы друг от друга.

Разница между «java.lang.OutOfMemoryError: пространство кучи Java» и «java.lang.OutOfMemoryError: пространство PermGen»

Если вы знакомы с разными поколениями в куче и с тем, как работает сборка мусора в Java, а также знаете о новых, старых и постоянных поколениях пространства кучи, то вы легко разберетесь с этой ошибкой OutOfMemoryError в Java. Постоянное создание кучи используется для хранения пула строк и различных метаданных, необходимых JVM, связанных с классом, методом и другими примитивами Java.

Поскольку большая часть размера Perm Space по умолчанию для JVM составляет около «64 МБ». вы можете легко исчерпать память, если у вас слишком много классов или огромное количество строк в вашем проекте.

Важно помнить, что это не зависит от значения –Xmx , поэтому независимо от того, насколько велик общий размер кучи, вы можете запускать OutOfMemory в постоянном пространстве. Хорошо, что вы можете указать размер постоянного поколения , используя параметры JVM «-XX: PermSize» и «-XX: MaxPermSize» в зависимости от потребностей вашего проекта.

Следует помнить одну небольшую вещь: «=» используется для разделения параметра и значения при указании размера постоянного пространства в куче , в то время как «=» не требуется, а устанавливает максимальный размер кучи в java , как показано в приведенном ниже примере.

экспорт JVM_ARGS=»-Xmx1024m -XX:MaxPermSize=256m»

Другой причиной ошибки « java.lang.OutOfMemoryError: PermGen » является утечка памяти через загрузчики классов, и она очень часто проявляется в WebServer и сервере приложений, таком как tomcat, WebSphere, Glassfish или WebLogic.

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

« java.lang.OutOfMemoryError: PermGen » много раз наблюдалось в tomcat в нашем последнем проекте, но решение этой проблемы действительно сложно, потому что сначала вам нужно знать, какой класс вызывает утечку памяти, а затем вам нужно исправить это Другая причина OutOfMemoryError в пространстве PermGen заключается в том, что любой поток, запущенный приложением, не завершается при отмене развертывания приложения.

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



Еще одна довольно неизвестная, но интересная причина «java.lang.OutOfMemoryError: PermGen», которую мы обнаружили, — это введение параметров JVM «-Xnoclassgc ».

Эта опция иногда используется, чтобы избежать загрузки и выгрузки классов, когда на них больше нет живых ссылок, просто чтобы избежать снижения производительности из-за частой загрузки и выгрузки, но использование этой опции в среде J2EE может быть очень опасным, потому что многие фреймворки, такие как Struts , spring и т. д. использует отражение для создания классов, и при частом развертывании и удалении вы можете легко исчерпать пространство в PermGen , если предыдущие ссылки не были очищены.Этот пример также указывает на то, что иногда неверные аргументы или конфигурация JVM могут вызывать OutOfMemoryError в Java.

Таким образом, следует избегать использования «-Xnoclassgc » в среде J2EE, особенно с AppServer.

Tomcat для устранения ошибки OutOfMemoryError в пространстве PermGen

Начиная с версии tomcat > 6.0, tomcat предоставляет функцию обнаружения утечек памяти, которая может обнаруживать многие распространенные утечки памяти с точки зрения веб-приложения, например.g Утечки памяти ThreadLocal, регистрация драйвера JDBC, цели RMI, LogFactory и Thread, порожденные веб-приложениями.

Вы можете проверить полную информацию на http://wiki.apache.org/tomcat/MemoryLeakProtection. Вы также можете обнаружить утечки памяти, обратившись к приложению-менеджеру, которое поставляется с tomcat, на случай, если у вас возникнет утечка памяти в любом веб-приложении Java. это хорошая идея, чтобы запустить его на tomcat.

Как решить java.lang.OutOfMemoryError: пространство кучи Java 1) Простой способ решить OutOfMemoryError в java — это увеличить максимальный размер кучи
до
с помощью параметров JVM «-Xmx512M», это немедленно решит вашу ошибку OutOfMemoryError.Это мое предпочтительное решение, когда я получаю OutOfMemoryError в Eclipse, Maven или ANT при создании проекта, потому что в зависимости от размера проекта вы можете легко исчерпать память. Вот пример увеличения максимального размера кучи JVM . Также лучше сохранить соотношение -Xmx к -Xms либо 1:1, либо 1:1.5, если вы устанавливаете размер кучи в своем Java-приложении

export JVM_ARGS= «-Xms1024m -Xmx1024m»

2) Второй способ устранения OutOfMemoryError в Java довольно сложен и возникает, когда у вас мало памяти и даже после увеличения максимального размера кучи вы все равно получаете java.lang.OutOfMemoryError, в этом случае вы, вероятно, захотите профилировать свое приложение и искать любую утечку памяти.

Вы также можете использовать Eclipse Memory Analyzer для проверки дампа кучи или любой профилировщик, такой как Netbeans или JProbe. Это сложное решение, требующее некоторого времени для анализа и поиска утечек памяти .

Как решить ошибку java.lang.OutOfMemoryError: пространство PermGen Как объяснялось в предыдущем абзаце, эта ошибка OutOfMemory в java возникает, когда постоянное создание кучи заполнено.Чтобы исправить эту ошибку OutOfMemoryError в Java, необходимо увеличить размер кучи пространства Perm с помощью параметра JVM    «-XX: MaxPermSize». Вы также можете указать начальный размер Perm Space, используя    «-XX: PermSize» , и, сохраняя как начальный , так и максимальный Perm Space , вы можете предотвратить некоторую полную сборку мусора, которая может произойти при изменении размера Perm Space. Вот , как вы можете указать начальный и максимальный размер Perm в Java.lang.OutOfMemoryError  в Java становится сложной задачей, и в этих случаях профилирование остается окончательным решением. Хотя у вас есть свобода увеличивать размер кучи в Java, рекомендуется следовать методам управления памятью при кодировании и устанавливать нуль для любых неиспользуемых ссылок.
На этом от меня все по OutOfMemoryError в Java Подробнее о поиске утечки памяти в java и использовании профилировщика я постараюсь написать в каком-нибудь другом посте. Поделитесь, пожалуйста, каков ваш подход к решению java.lang.OutOfMemoryError в Java .

Важное примечание: Начиная с Tomcat > 6.0, tomcat предоставляет функцию обнаружения утечек памяти, которая может обнаруживать многие распространенные утечки памяти в приложениях Java, например утечки памяти ThreadLocal, регистрацию драйвера JDBC, цели RMI, LogFactory и потоки, порожденные веб-приложениями. Вы можете проверить полную информацию на http://wiki.apache.org/tomcat/MemoryLeakProtection.

Вы также можете обнаружить утечку памяти, обратившись к приложению-менеджеру, которое поставляется с tomcat. Если вы испытываете утечку памяти в любом веб-приложении Java, рекомендуется запустить его на tomcat, чтобы выяснить причину OutOfMemoryError в пространстве PermGen. .

Инструменты для исследования и исправления OutOfMemoryError в Java

Java.lang.OutOfMemoryError — это своего рода ошибка, которая требует тщательного изучения, чтобы выяснить основную причину проблемы, какой объект занимает память, сколько памяти он занимает или найти страшную утечку памяти, и вы не можете этого сделать. без знания доступных инструментов в пространстве Java. Здесь я перечисляю некоторые бесплатные инструменты, которые можно использовать для анализа кучи и которые помогут вам найти виновника ошибки OutOfMemoryError

.

1.

Визуалгк

Visualgc расшифровывается как Visual Garbage Collection Monitoring Tool, и вы можете подключить его к JVM с инструментированной точкой доступа. Основная сила visualgc заключается в том, что он графически отображает все ключевые данные, включая загрузчик классов, сборку мусора и данные о производительности компилятора JVM.

Целевая JVM идентифицируется ее идентификатором виртуальной машины, также называемым vmid. Подробнее о параметрах visualgc и vmid можно прочитать здесь.

2.карта

Инструмент jmap — это утилита командной строки, поставляемая с JDK6 и позволяющая создавать дамп памяти кучи в файле. Его легко использовать, как показано ниже:

jmap -dump:format=b,file=heapdump 6054

Здесь файл указывает имя файла дампа памяти, который является «дампом кучи», а 6054 — это PID вашего прогресса Java. Вы можете найти PDI с помощью «ps -ef» или диспетчера задач Windows или с помощью инструмента под названием «jps» (инструмент состояния процесса виртуальной машины Java).

3. джхат

Инструмент jhat ранее был известен как hat (инструмент для анализа кучи), но теперь он является частью JDK6. Вы можете использовать jhat для анализа файла дампа кучи, созданного с помощью « jmap ». jhat также является утилитой командной строки, и вы можете запустить ее из окна cmd, как показано ниже:

jhat -J-Xmx256m дамп памяти

Здесь он проанализирует дамп памяти, содержащийся в файле «heapdump». Когда вы запустите jhat , он прочитает этот файл дампа кучи, а затем начнет прослушивать порт HTTP, просто укажите в браузере порт, где jhat прослушивает по умолчанию 7000, и тогда вы можете начать анализировать объекты, присутствующие в дампе кучи.

4. Анализатор памяти Eclipse

Анализатор памяти Eclipse (MAT) — это инструмент от Eclipse Foundation для анализа дампа кучи Java. Это помогает найти утечки загрузчика классов и памяти, а также помогает минимизировать потребление памяти. Вы можете использовать MAT для анализа дампа кучи, содержащего миллионы объектов, а также для извлечения подозреваемых в утечке памяти. Смотрите здесь для получения дополнительной информации.

5. Книги для изучения профилирования По мере того, как ваш опыт работы с Java растет, ожидания также растут с точки зрения нишевых навыков, таких как анализ проблем с производительностью и удобство профилирования.Обычно вы не освоите эти навыки, если не приложите дополнительных усилий. Чтобы эффективно справляться с такими ошибками, как java.lang.OutOfMemoryError, вам следует прочитать хорошие книги по устранению неполадок и настройке производительности, например. Производительность Java Полное руководство Скотта Оукса, как показано ниже:


Вот некоторые из моих других сообщений о Java, которые могут вас заинтересовать:


Спасибо, что прочитали эту статью. Если вы считаете эту статью полезной и информативной, поделитесь ею с друзьями и коллегами в Facebook и Twitter.

Стек против кучи Java — Javatpoint

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

Память стека

Память стека — это физическое пространство (в ОЗУ), выделяемое каждому потоку во время выполнения. Он создается при создании потока. Управление памятью в стеке следует порядку LIFO (последним пришел — первым вышел), потому что оно доступно глобально. В нем хранятся переменные, ссылки на объекты и частичные результаты. Память, выделенная для стека, существует до тех пор, пока функция не вернется. Если нет места для создания новых объектов, он бросает java.язык.StackOverFlowError. Объем элементов ограничен их потоками. JVM создает отдельный стек для каждого потока.

Куча памяти

Создается при запуске JVM и используется приложением, пока приложение работает. В нем хранятся объекты и классы JRE. Всякий раз, когда мы создаем объекты, они занимают место в куче памяти, в то время как ссылка на этот объект создается в стеке. Он не следует какому-либо порядку, как стек. Он динамически обрабатывает блоки памяти.Это означает, что нам не нужно обрабатывать память вручную. Для автоматического управления памятью Java предоставляет сборщик мусора, который удаляет объекты, которые больше не используются. Память, выделенная для кучи, существует до тех пор, пока не произойдет какое-либо одно событие, будь то завершение программы или освобождение памяти. Элементы глобально доступны в приложении. Это общее пространство памяти, совместно используемое всеми потоками. Если пространство кучи заполнено, выдается ошибка java.lang.OutOfMemoryError. Куча памяти далее делится на следующие области памяти:

  • Молодое поколение
  • Место для выживших
  • Старое поколение
  • Постоянное поколение
  • Кэш кода

На следующем рисунке показано распределение памяти стека и кучи.

Разница между стеком и динамической памятью

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

Параметр Память стека Куча пространства
Применение В нем хранятся элементы с очень коротким сроком службы, такие как методы , переменные и ссылочные переменные объектов. В нем хранится объектов и классы Java Runtime Environment ( JRE ).
Заказ Следует порядку LIFO . Не следует порядку, так как это динамическое выделение памяти и не имеет фиксированного шаблона для выделения и освобождения блоков памяти.
Гибкость Это не гибко , потому что мы не можем изменить выделенную память. Это гибкий , потому что мы можем изменить выделенную память.
Эффективность Он имеет более быстрый доступ, выделение и освобождение. Имеет более медленный доступ, выделение и освобождение.
Объем памяти На меньше по размеру. Это больше по размеру.
Используемые параметры Java Мы можем увеличить размер стека, используя параметр JVM -Xss. Мы можем увеличить или уменьшить размер динамической памяти, используя параметры JVM -Xmx и -Xms.
Видимость или объем Переменные видны только потоку-владельцу. Видно всем потокам.
Поколение космоса При создании потока операционная система автоматически выделяет стек. Чтобы создать пространство кучи для приложения, язык сначала вызывает операционную систему во время выполнения.
Распределение Для каждого объекта создается отдельный стек. Он является общим для всех потоков.
Вызов исключения JVM выдает ошибку java.lang.StackOverFlowError , если размер стека превышает ограничение. Чтобы избежать этой ошибки, увеличьте размер стека. JVM выдает ошибку java.lang.OutOfMemoryError , если JVM не может создать новый собственный метод.
Распределение/освобождение Это делается автоматически компилятором . Делается вручную программатором .
Стоимость Стоимость минус . Его стоимость на больше на по сравнению со стопкой.
Реализация Его реализация жесткая . Его реализация проста .
Порядок распределения Распределение памяти непрерывно . Память выделена в произвольном порядке.
Защита от резьбы Это потокобезопасно, поскольку у каждого потока есть собственный стек. Он не является потокобезопасным, поэтому требуется правильная синхронизация кода.

База знаний Java Profiler — Структура памяти JVM

Не можете найти свой ответ? Пожалуйста, обратитесь к документации и демонстрациям, задайте свой вопрос на форуме или обратитесь в службу поддержки.

Как показал опыт, иногда может возникать своего рода неопределенность в отношении Структура памяти виртуальной машины Java (JVM) и другие связанные аспекты, такие как размеры различные виды памяти, живые и мертвые объекты и т. д.

В этой статье мы попытаемся осветить эти вопросы, чтобы прояснить суть.

Куча и не куча памяти

Память JVM состоит из следующих сегментов:

  • Куча памяти, которая является хранилищем для объектов Java
  • Память без кучи, которая используется Java для хранения загруженных классов и других метаданных
  • Сам код JVM, внутренние структуры JVM, загруженный код и данные агента профилировщика и т. д.


Куча

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

Размер кучи можно настроить с помощью следующих параметров виртуальной машины:

  • -Xmx — установить максимальный размер кучи Java
  • -Xms — установить начальный размер кучи Java

По умолчанию максимальный размер кучи составляет 64 МБ.

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

без кучи

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

К сожалению, единственная информация, которую JVM предоставляет о памяти без кучи, — это ее общий размер. Подробная информация о содержимом памяти вне кучи отсутствует.

Аномальный рост объема памяти без кучи может указывать на потенциальную проблему. в этом случае вы можете проверить следующее:

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

Если приложению действительно требуется столько памяти, не относящейся к куче, а максимального размера по умолчанию в 64 МБ недостаточно, вы можете увеличить максимальный размер с помощью опции -XX:MaxPermSize VM. Например, -XX:MaxPermSize=128m задает размер 128 Мб.

Телеметрия использования кучи и некучи памяти отображается на вкладке Память:


Выделенная и используемая память

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

выделенная память общий объем памяти, выделенный JVM, а используемая память объем памяти который реально используется.

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

Используемая динамическая память: живые и мертвые объекты

Используемая память кучи состоит из живых и мертвых объектов.

Живые объекты доступны приложению и будут не быть предметом вывоза мусора.

Мертвые объекты — это те, которые никогда не будут доступны приложением, но еще не собраны сборщиком мусора. Такие объекты занимают пространство кучи памяти, пока они не будут в конечном итоге собраны сборщиком мусора.

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

Размеры объектов в моментальных снимках памяти: мелкие и сохраненные размеры

Все отдельные объекты, а также наборы объектов имеют свои мелкие и сохраненные размеры.

Неглубокий размер объекта — это объем выделенной памяти для хранения самого объекта, без учета ссылочных объектов.Неглубокий размер обычного (немассивного) объекта зависит от количество и типы его полей. Неглубокий размер массива зависит от длины массива и типа его элементы (объекты, примитивные типы). Неглубокий размер набора объектов представляет собой сумму неглубоких размеров всех предметов набора.

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

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

Подробнее о неглубоких и фиксированных размерах.

Как JVM использует и распределяет память

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

Отслеживание использования памяти в JVM

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

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

Затем протестируйте приложение с ожидаемой производственной нагрузкой в ​​среде разработки, чтобы определить максимальное использование памяти кучи. Размер вашей рабочей кучи должен быть как минимум на 25–30 % выше протестированного максимума, чтобы оставить место для накладных расходов.

Обнаружение ошибок нехватки памяти

При включении параметра -XX:HeapDumpOnOutOfMemoryError создается дамп кучи, когда выделение из кучи Java не может быть выполнено и приложение завершается с ошибкой OutOfMemoryError . Дамп кучи может помочь вам найти причину.

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

По умолчанию дамп кучи создается в файле с именем java_pidpid.hprof в рабочем каталоге JVM. Вы можете указать альтернативное имя файла или каталог с параметром -XX:HeapDumpPath . Вы также можете наблюдать пиковое использование памяти кучи с помощью JConsole. Также см. раздел Как отслеживать использование памяти Java для кучи/постоянной генерации и операций gc.

Анализ дампа кучи

Дамп кучи может показать, долго ли сохранялись большие объекты.Иногда, чтобы найти причину задержки, необходимо понять код приложения и обстоятельства, вызвавшие ошибку нехватки памяти. Проблема может быть вызвана утечкой памяти в коде приложения; в противном случае проблема, вероятно, заключается в недостаточном объеме памяти, выделенной для пиковых нагрузок приложения.

Причины хранения крупных объектов включают:

  • Отдельные объекты, занимающие всю память кучи, выделенную для JVM.
  • Много мелких объектов, сохраняющих память.
  • Большое удержание одним потоком, возможно, потоком, связанным с OutOfMemoryError .

После того, как вы определили источник большого удержания, просмотрите пути от корней сборки мусора, чтобы увидеть, что поддерживает жизнь объектов. Корни сборки мусора — это объекты вне кучи, поэтому они никогда не собираются. Путь к корням сборки мусора показывает цепочку ссылок, которая предотвращает сборку мусора для объекта в куче.Статья 10 советов по использованию Eclipse Memory Analyzer предлагает советы по анализу дампов кучи и утечек памяти в Eclipse Memory Analyzer.

Дополнительные сведения об анализе дампа кучи см. в разделе Как анализировать дамп кучи Java. Если проблема не в коде приложения, увеличьте размер кучи Java в соответствии с требованиями нагрузки.

Параметры JVM, влияющие на использование памяти

Параметры, влияющие на память, доступную для JVM, включают:

  • -Xms : устанавливает минимальный и начальный размер кучи.
  • -Xmx : устанавливает максимальный размер кучи.
  • -XX:PermSize : устанавливает начальный размер области памяти постоянного поколения ( perm ). Этот параметр был доступен до JDK 8, но больше не поддерживается.
  • -XX:MaxPermSize : Задает максимальный размер постоянной области памяти. Этот параметр был доступен до JDK 8, но больше не поддерживается.
  • -XX:MetaspaceSize : устанавливает начальный размер метапространства.Этот параметр доступен, начиная с JDK 8.
  • -XX:MaxMetaspaceSize : устанавливает максимальный размер метапространства. Этот параметр доступен, начиная с JDK 8.

В рабочих средах для параметров -Xms и -Xmx часто устанавливаются одинаковые значения, чтобы размер кучи был фиксированным и предварительно выделялся для JVM.

Дополнительную информацию о различных параметрах JVM и их использовании см. в списке параметров java-команд Oracle.

Расчет потребления памяти JVM

Многие программисты правильно определяют максимальное значение кучи для JVM своего приложения, но обнаруживают, что JVM использует еще больше памяти.Значение параметра -Xmx указывает максимальный размер кучи Java, но это не единственная память, потребляемая JVM. Постоянное поколение (название до JDK 8) или Metaspace (название, начиная с JDK 8), CodeCache, собственная куча C++, используемая другими внутренними компонентами JVM, пространство для стеков потоков, прямые байтовые буферы, накладные расходы на сборку мусора и другие вещи учитываются как часть потребления памяти JVM.

Вы можете рассчитать объем памяти, используемый процессом JVM, следующим образом:

  Память JVM = куча памяти + метапространство + CodeCache + (ThreadStackSize * количество потоков) + DirectByteBuffers + Jvm-native
  

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

Компоненты потребления памяти JVM

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

  • Metaspace : хранит информацию о классах и методах, используемых в приложении. Эта область хранения называлась Permanent Generation или perm в HotSpot JVM до JDK 8, и эта область была смежной с кучей Java. Начиная с JDK 8, постоянное поколение было заменено метапространством, которое не является смежным с кучей Java.Метапространство размещается в собственной памяти. Параметр MaxMetaspaceSize ограничивает использование JVM метапространства. По умолчанию для Metaspace нет ограничений, он начинается с очень маленького размера по умолчанию и постепенно увеличивается по мере необходимости. Метапространство содержит только метаданные класса; все живые объекты Java перемещаются в динамическую память. Таким образом, размер Metaspace намного меньше, чем у Permanent Generation. Обычно нет необходимости указывать максимальный размер метапространства, если вы не сталкиваетесь с большой утечкой метапространства.
  • CodeCache : Содержит собственный код, сгенерированный JVM. JVM создает собственный код по ряду причин, включая динамически генерируемый цикл интерпретатора, заглушки Java Native Interface (JNI) и методы Java, которые компилируются в собственный код компилятором Just-in-Time (JIT). Компилятор JIT вносит основной вклад в область CodeCache.
  • ThreadStackSize : задает размер стека потоков в байтах с помощью параметра -XX:ThreadStackSize= , который также можно указать как -Xss= .Добавьте букву k или K для обозначения килобайт; м или м для обозначения мегабайт; или g или G для обозначения гигабайт. Значение по умолчанию для -XX:ThreadStackSize зависит от базовой операционной системы и архитектуры.

Как проверить размер стека потоков

Вы можете проверить текущий размер стека потоков с помощью:

  $ jinfo -flag ThreadStackSize JAVA_PID  

Используйте следующую команду, чтобы проверить размер стека потока по умолчанию:

  $ java -XX:+PrintFlagsFinal -version |grep ThreadStackSize  

На рис. 1 показаны размеры стека потоков, отображаемые предыдущей командой.

Рисунок 1: Проверка размеров стеков потоков, используемых вашей программой.

Заключение

Настройка размера кучи в соответствии с требованиями приложения — это первый шаг в настройке параметров кучи. Опция ограничивает размер кучи. Однако для JVM требуется больше памяти, чем размер кучи, включая Metaspace, CodeCache, размер стека потоков и собственную память.Поэтому, когда вы рассматриваете использование памяти JVM, не забудьте включить эти другие части потребления памяти.

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

Пространство кучи Java: советы по настройке производительности Java

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

Ниже приведены несколько простых советов по настройке производительности Java, которым вы можете следовать в процессе разработки:

  • Распределение памяти JVM

    Когда объем используемой памяти достигает порогового значения, JVM создает исключение java.lang.OutOfMemoryError. Это исключение в основном возникает из-за возможных причин, перечисленных ниже:

    • Недостаточно места в JavaHeap для создания новых объектов. Это может быть увеличено за счет -Xmx (java.lang.OutOfMemoryError: пространство кучи Java).java.lang.OutOfMemoryError: пространство кучи Java
    • Постоянная генерация может быть низкой. Это может быть увеличено с помощью XX:MaxPermSize=256m(java.lang.OutOfMemoryError: пространство PermGen)java.lang.OutOfMemoryError: пространство PermGen
    • Место подкачки может закончиться из-за java.lang.OutOfMemoryError

    Куча Java Native Interface (JNI) не хватает памяти, даже если у JavaHeap и PermGen есть память.Это обычно происходит, если вы делаете много тяжелых вызовов JNI, но объекты JavaHeap занимают мало места. В этом сценарии поток сборщика мусора (GC) может не чувствовать необходимости очищать память JavaHeap, в то время как куча JNI продолжает увеличиваться, пока не закончится память.

    Рекомендуется увеличивать пространство кучи Java только до половины общего объема ОЗУ, доступного на сервере. Увеличение пространства кучи Java сверх этого значения может привести к проблемам с производительностью. Например, если на вашем сервере доступно 16 ГБ ОЗУ, максимальное пространство кучи, которое вы должны использовать, составляет 8 ГБ.

    Если вы используете Tomcat, выполните следующие действия:

    Откройте файл catalina.bat (TomcatInstallDirectory/bin/catalina.bat).

    Установить JAVA_OPTS=%JAVA_OPTS% -Xms1024m -Xmx1024m

    [Где Xms — начальный (начальный) пул памяти, а Xmx — максимальный пул памяти]

  • Программное объединение строк

    StringBuilder можно использовать для программного объединения строк. В Java существует множество различных вариантов объединения строк.Вы можете, например, использовать простой «+» или «+=», старый добрый StringBuffer или StringBuilder.

  • Использование примитивов

    , где это возможно. Еще один быстрый и простой способ избежать каких-либо накладных расходов и повысить производительность вашего приложения — использовать примитивные типы вместо их классов-оболочек. Итак, лучше использовать «int» вместо Integer или «double» вместо Double. Это позволяет вашей JVM хранить значение в стеке, а не в куче, чтобы уменьшить потребление памяти и в целом более эффективно обрабатывать его.

  • Избегание BigInteger и BigDecimal

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

  • Проверка текущих уровней журнала

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

  • Избегайте «+» для объединения строк в одном выражении

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

  • Использование ArrayList вместо LinkedList

    Внутренний обход узла ArrayList от начала до конца коллекции значительно быстрее, чем обход LinkedList. Следовательно, запросы, реализованные в классе, могут выполняться быстрее. Обход итератором всех элементов быстрее для ArrayList по сравнению с LinkedList. ArrayLists могут генерировать гораздо меньше объектов для восстановления сборщиком мусора по сравнению с LinkedLists.

  • Использование пользовательских методов преобразования для преобразования между типами данных

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

  • Не оптимизируйте, пока не узнаете, что это необходимо

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

  • Понимание реальной стоимости потоков

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

  • Осознание затрат на форматирование и синтаксический анализ

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

  • Используйте Apache Commons StringUtils.replace вместо string.replace

    В целом, метод string.replace работает нормально и эффективен, особенно если вы используете Java. Но если ваше приложение требует большого количества операций замены, а вы не обновились до последней версии Java, все равно имеет смысл поискать более быстрые и эффективные альтернативы.

  • Итерация по рекурсии

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

  • Использование EnumSet или EnumMap вместо HashSet и HashMap

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

  • Избегайте повторных вызовов, когда известен размер коллекции

    Iterator.hasNext() и Enumerator.hasMoreElements() не нужно повторно вызывать, если известен размер коллекции. Вместо этого используйте collection.size() и счетчик циклов.

  • Оптимизируйте методы hashCode() и equals()

    Хороший метод hashCode() необходим, потому что он предотвратит дальнейшие вызовы гораздо более дорогого equals(), поскольку он будет создавать более разные сегменты хэша для каждого набора экземпляров.

  • Хорошо используйте блок «наконец»

    Блок «finally» — ключевой инструмент для предотвращения утечек ресурсов. При закрытии файла или ином восстановлении ресурсов поместите код в блок «finally», чтобы гарантировать, что ресурс всегда будет восстановлен. Но «finally» полезен не только для обработки исключений — он позволяет программисту избежать случайного обхода кода очистки с помощью «return», «continue» или «break». Размещение кода очистки в блоке «finally» всегда является хорошей практикой, даже если исключений не ожидается.

  • Используйте профилировщик, чтобы найти узкое место

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

  • .

    Post A Comment

    Ваш адрес email не будет опубликован.