Java память: Стек и куча в Java
Распределение памяти в JVM / Блог компании OTUS. Онлайн-образование / Хабр
Всем привет! Перевод сегодняшнего материала мы хотим приурочить к запуску нового потока по курсу «Разработчик Java», который стартует уже завтра. Что ж начнём.
JVM может быть сложным зверем. К счастью, большая часть этой сложности скрыта под капотом, и мы, как разработчики приложений и ответственные за деплой, часто не должны об этом сильно беспокоиться. Хотя из-за роста популярности технологий развертывания приложений в контейнерах, стоит обратить внимание на распределение памяти в JVM.
Два вида памяти
JVM разделяет память на две основные категории: «кучу» (heap) и «не кучу» (non-heap). Куча — это часть памяти JVM, с которой разработчики наиболее знакомы. Здесь хранятся объекты, созданные приложением. Они остаются там до тех пор, пока не будут убраны сборщиком мусора. Как правило, размер кучи, которую использует приложение, изменяется в зависимости от текущей нагрузки.
Память вне кучи делится на несколько областей. В HotSpot для изучения областей этой памяти можно использовать механизм Native memory tracking (NMT). Обратите внимание, что, хотя NMT не отслеживает использование всей нативной памяти (например, не отслеживается выделение нативной памяти сторонним кодом), его возможностей достаточно для большинства типичных приложений на Spring. Для использования NMT запустите приложение с параметром -XX:NativeMemoryTracking=summary
и с помощью jcmd VM.native_memory summary посмотрите информацию об используемой памяти.
Давайте посмотрим использование NMT на примере нашего старого друга Petclinic. Диаграмма ниже показывает использование памяти JVM по данным NMT (за вычетом собственного оверхеда NMT) при запуске Petclinic с максимальным размером кучи 48 МБ (-Xmx48M
):
Как вы видите, на память вне кучи приходится большая часть используемой памяти JVM, причем память кучи составляет только одну шестую часть от общего объёма. В этом случае это примерно 44 МБ (из которых 33 МБ использовалось сразу после сборки мусора). Использование памяти вне кучи составило в сумме 223 МБ.
Области нативной памяти
Compressed class space (область сжатых указателей): используется для хранения информации о загруженных классах. Ограничивается параметром MaxMetaspaceSize
. Функция количества классов, которые были загружены.
Примечание переводчика
Почему-то автор пишет про «Compressed class space», а не про всю область «Class». Область «Compressed class space» входит в состав области «Сlass», а параметр
MaxMetaspaceSize
ограничивает размер всей области «Class», а не только «Compressed class space». Для ограничения «Compressed class space» используется параметрCompressedClassSpaceSize
.Отсюда:
IfUseCompressedOops
is turned on andUseCompressedClassesPointers
is used, then two logically different areas of native memory are used for class metadata…
A region is allocated for these compressed class pointers (the 32-bit offsets). The size of the region can be set withCompressedClassSpaceSize
and is 1 gigabyte (GB) by default…
TheMaxMetaspaceSize
applies to the sum of the committed compressed class space and the space for the other class metadataЕсли включен параметр
UseCompressedOops
и используетсяUseCompressedClassesPointers
, тогда для метаданных классов используется две логически разные области нативной памяти…Для сжатых указателей выделяется область памяти (32-битные смещения). Размер этой области может быть установлен
CompressedClassSpaceSize
и по умолчанию он 1 ГБ…
ПараметрMaxMetaspaceSize
относится к сумме области сжатых указателей и области для других метаданных класса.
- Thread (потоки): память, используемая потоками в JVM. Функция количества запущенных потоков.
- Code cache (кэш кода): память, используемая JIT для его работы. Функция количества классов, которые были загружены. Ограничивается параметром
ReservedCodeCacheSize
. Можно уменьшить настройкой JIT, например, отключив многоуровневую компиляцию (tiered compilation). - GC (сборщик мусора): хранит данные, используемые сборщиком мусора. Зависит от используемого сборщика мусора.
- Symbol (символы): хранит такие символы, как имена полей, сигнатуры методов и интернированные строки. Чрезмерное использование памяти символов может указывать на то, что строки слишком интернированы.
- Internal (внутренние данные): хранит прочие внутренние данные, которые не входят ни в одну из других областей.
Отличия
По сравнению с кучей, память вне кучи меньше изменяется под нагрузкой. Как только приложение загрузит все классы, которые будут использоваться и JIT полностью прогреется, всё перейдет в устойчивое состояние. Чтобы увидеть уменьшение использования области Compressed class space, загрузчик классов, который загрузил классы, должен быть удален сборщиком мусора. Это было распространено в прошлом, когда приложения развертывались в контейнерах сервлетов или серверах приложений (загрузчик классов приложения удалялся сборщиком мусора, когда приложение удалялось с сервера приложений), но с современными подходами к развертыванию приложений это случается редко.
Настройка JVM
Настроить JVM для эффективного использования доступной оперативной памяти непросто. Если вы запустите JVM с параметром -Xmx16M
и ожидаете, что будет использоваться не более 16 МБ памяти, то вас ждёт неприятный сюрприз.
Интересной областью памяти JVM является кэш кода JIT. По умолчанию HotSpot JVM будет использовать до 240 МБ. Если кэш кода слишком мал, в JIT может не хватить места для хранения своих данных, и в результате будет снижена производительность. Если кэш слишком велик, то память может быть потрачена впустую. При определении размера кэша важно учитывать его влияние как на использование памяти, так и на производительность.
При работе в контейнере Docker последние версии Java теперь знают об ограничениях памяти контейнера и пытаются соответствующим образом изменить размер памяти JVM. К сожалению, часто происходит выделение большого количества памяти вне кучи и недостаточного в куче. Допустим, у вас есть приложение, работающее в контейнере с 2-мя процессорами и 512 МБ доступной памяти. Вы хотите, чтобы обрабатывалось больше нагрузки и увеличиваете количество процессоров до 4-х и память до 1 ГБ. Как мы обсуждали выше, размер кучи обычно изменяется в зависимости от нагрузки, а память вне кучи изменяется значительно меньше. Поэтому мы ожидаем, что большая часть дополнительных 512 МБ будет предоставлена куче, чтобы справиться с увеличенной нагрузкой. К сожалению, по умолчанию JVM этого не сделает и распределит дополнительную память более менее равномерно между памятью в куче и вне кучи.
К счастью, команда CloudFoundry обладает обширными знаниями о распределении памяти в JVM. Если вы загружаете приложения в CloudFoundry, то сборщик (build pack) автоматически применит эти знания для вас. Если вы не используете CloudFoudry или хотели бы больше понять о том, как настроить JVM, то рекомендуется прочитать описание третьей версии Java buildpack’s memory calculator.
Что это значит для Spring
Команда Spring проводит много времени, думая о производительности и использовании памяти, рассматривая возможность использования памяти как в куче, так и вне кучи. Один из способов ограничить использование памяти вне кучи — это делать части фреймворка максимально универсальными. Примером этого является использование Reflection для создания и внедрения зависимостей в бины вашего приложения. Благодаря использованию Reflection количество кода фреймворка, который вы используете, остается постоянным, независимо от количества бинов в вашем приложении. Для оптимизации времени запуска мы используем кэш в куче, очищая этот кэш после завершения запуска. Память кучи может быть легко очищена сборщиком мусора, чтобы предоставить больше доступной памяти вашему приложению.
Традиционно ждём ваши комментарии по материалу.
Java управление памятью стека и кучи
Прежде всего: я предполагаю, что ваши вопросы появятся после прочтения этой статьи ( потому что там я вижу очень похожую диаграмму, как и Ваша), поэтому я не буду цитировать или выделять какие-либо пункты, которые упоминаются там, и постараюсь ответить на ваши вопросы точками, которые не были столь очевидны в этом посте.
Читая все ваши вопросы, у меня сложилось впечатление, что вы ясно представляете, как распределяется память в стеке и куче, но сомневаетесь в метаданных классов, т. е. где в памяти будут храниться методы классов и как они будут переработаны. Итак, сначала позвольте мне попытаться объяснить JVM области памяти:
Позвольте мне начать с того, что я помещу эти 2 диаграммы, изображающие области памяти JVM:
Источник схема
Источник схема
Теперь, как видно из приведенных выше диаграмм, ниже приведена древовидная структура памяти JVM, и я попытаюсь пролить свет на то же самое ( @Adit: обратите внимание, что область, которая вас беспокоит, — это пространство PermGen или постоянное пространство генерации памяти без кучи ).
- Кучная память
- Молодое поколение
- Пространства Рая
- Пространство Оставшегося В Живых
- Старое поколение
- Штатное Поколение
- Молодое поколение
- NonHeap память
- Постоянной Генерации
- Кэш кода ( я думаю, что включал «only» по HotSpot Java VM )
Кучная память
Кучная память — это область данных времени выполнения, из которой Java VM выделяет память для всех экземпляров класса и массивов. Куча может быть фиксированного или переменного размера. Сборщик мусора-это автоматическая система управления памятью, которая восстанавливает память кучи для объектов.
Молодое поколение
Молодое поколение — это место, где создаются все новые объекты. Когда молодое поколение заполняется, производится вывоз мусора. Сборщик мусора вызывается незначительными GC. Молодое поколение делится на 2 части
Eden space: пул, из которого изначально выделяется память для большинства объектов.
Пространство выживших: пул, содержащий объекты, которые пережили сбор мусора в пространстве Эдема.
Старое поколение
Память старого поколения содержит объекты, которые долго жили и выжили после многих раундов минорных GC. Обычно сбор мусора выполняется в памяти старого поколения, когда она заполнена. Сборка мусора старого поколения называется Major GC и обычно занимает больше времени. Старое поколение содержит нижеприведенную часть:
Арендуемое пространство: пул, содержащий объекты, которые существовали в течение некоторого времени в пространстве выживших.
Память не «кучи»
Память без кучи включает в себя область метода, разделяемую между всеми потоками, и память, необходимую для внутренней обработки или оптимизации для Java VM. Он хранит структуры для каждого класса, такие как пул констант времени выполнения, данные полей и методов, а также код для методов и конструкторов. Область метода логически является частью кучи, но, в зависимости от реализации, a Java VM не может собирать мусор или компактировать его. Как и память кучи, область метода может иметь фиксированный или переменный размер. Память для области метода не обязательно должна быть смежной.
Постоянной генерации
Пул, содержащий все отражающие данные самой виртуальной машины, такие как объекты класса и метода. С Java VMs, которые используют совместное использование данных класса, это поколение делится на области только для чтения и чтения-записи.
Кэш кода
HotSpot Java VM также включает в себя кэш кода, содержащий память, которая используется для компиляции и хранения собственного кода.
Где хранятся методы s?
Память без кучи —> постоянная генерация
Я создал еще один объект внутрь MemoryClass myMethod, будет JVM
снова выделить память для тех же методов внутри стековой памяти?
Память стека содержит только локальные переменные, поэтому ваш ORV (объектная ссылочная переменная) нового MemoryClass
все равно будет создан в кадре стека myMethod
, но JVM не будет снова загружать все методы, метаданные и т. д. MemoryClass
в «Permanent Generation».
JVM загружает класс только один раз, и когда он загружает класс, то пространство выделяется на «Permanent Generation» для этого класса, и это происходит только один раз, когда класс загружается JVM.
Освободит ли JVM память, выделенную myMethod, как только она
выполнение завершено, если да, то как бы он справился с ситуацией
, упомянутой в вопросе 2(применимо только в том случае, если JVM выделяет память
несколько раз одному и тому же методу).
Кадр стека, созданный для myMethod
, будет удален из памяти стека, поэтому вся память, созданная для локальных переменных, будет очищена, но это не означает, что JVM очистит память, выделенную в «Permanent Generation» для класса те объекты, которые вы создали в myMethod
Что было бы, если бы я только объявил s и не
инициализировал его, будет ли JVM по-прежнему выделять память всем методам
java.lang.String класс, если да, то почему?
В частности, говоря о классе String
, JVM выделил бы место для String
в «Permanent Generation» слишком рано, в то время как JVM запущен, и инициализируете ли вы свою строковую переменную или нет, с точки зрения «Permanent Generation» это не имеет значения.
Говоря о других пользовательских классах, JVM загружает класс и выделяет память в «Permanent Generation», как только вы определяете класс, опять же, даже если вы не создаете объект класса, память выделяется в «Permanent Generation» (область без кучи), а когда вы создаете объект класса, то память выделяется в «Eden Space» ( область кучи ).
Устройство памяти Java-процесса. — floppyy blog
Если вы читаете эту статью, значит вы уже работаете с Java и не хотите останавливаться на простом знании синтаксиса языка, стандартных библиотек и популярных Java-фреймворков — вы действительно хотите знать, как работает JVM, как выделяется память и происходит процесс сборки мусора.
В данной статье я хочу познакомить вас с устройством памяти в Java. Отсюда вы узнаете, где в Java хранятся локальные переменные, создаются объекты и как регулировать размер различных областей памяти для Java-процесса.
Итак, давайте сначала взглянем из каких областей состоит память, выделяемая JVM для вашей программы.
Java Heap.
А теперь давайте поподробнее. Память под объекты, которые вы создаете в своем приложении JVM выделяет из области, называемой кучей (heap), которая разделена на части — так называемый Young Generation и Old Generation. Зачем делить всю память на две части?
Более подробно мы поговорим об этом в статье, посвященной механизму сборки мусора в JVM. Сейчас я объясню это совсем коротко: сборка мусора в Java основывается на так называемой слабой гипотезе о поколениях, которая гласит о том, что большинство создаваемых в программе объектов умирают рано: это всевозможные локальные переменные, временные объекты, которые быстро отживают свое и больше не нужны. Garbage collector’у (сборщику мусора) нужно подобрать их в одном из следующих проходов и освободить память.
Изначально все объекты находятся в Young Generation. Но другие объекты (меньшая их часть), которые могут существовать длительное время или на протяжении жизни всего приложения, не нужно удалять. На эти объекты постоянно нужны «живые» ссылки. Сборщик мусора не удаляет их до тех пор, пока ссылки на них еще существуют. Они переживают одну за другой сборку мусора и наконец перемещаются во вторую часть Java-Heap’а — Old Generation, откуда с гораздо меньшей вероятностью будут когда-либо удалены (конечно, все зависит от специфики вашей программы и размера кучи, см. ниже).
Что делать, если вы получили Out of Memory Error: java heap space и уверены в том, что ваша программа не страдает от утечек памяти или неэффективного кода?
Вы можете регулировать размер кучи, запустив вашу программу со следующими параметрами: -Xms(минимальный размер Java heap’а) или —Xmx(максимальный размер Java heap’a).
Permanent Generation.
При первой загрузке вашего класса JVM создает объект Class. Это метаинформация, необходимая для создания новых объектов в Java, хранится как раз в области памяти,называемой Permanent Generation. Код ваших методов также хранится здесь, как и все статические переменные и методы.
И еще: теперь, получив в процессе выполнения вашей программы исключение Out of Memory Error: PermGen space, вы будете знать, размер какой именно области памяти Java-процесса надо увеличить. Для этого используйте параметры запуска программы: —XX:PermSize(начальный размер PermGen) или -XX:MaxPermSize(максимальный размер PermGen).
Java Stack.
Java — это стековая виртуальная машина. Надеюсь, что вам не нужно объяснять, что такое стек. Если вы все же нуждаетесь в том, чтобы освежить свои знания, обратитесь к этой статье.
Итак, JVM загружает в стек все локальные переменные и параметры вызываемых методов. При дальнейшем выполнении программы она их оттуда забирает, осуществляет нужные действия и кладет результат обратно в стек или просто извлекает следующий операнд. Также нужно знать,что стек делится на несколько областей, соответствующих каждому выполняемому потоку в вашей программе.
Если всем потокам в вашей программе нужно больше памяти и вы хотите избежать StackOverflowError, то имеет смысл увеличить размер стека, используя параметр -Xss. Отдельно следует уделить внимания потокам,в которых возможны рекурсивные вызовы методов. Также, для более точной регулировки размера доступной памяти для каждого потока, можно воспользоваться одним из конструкторов класса Thread(ThreadGroup group, Runnable target, String name, long stackSize), что может оказаться более разумным решением. Последний параметр stackSize говорит сам за себя.
Я сказал, что в стеке хранятся все локальные переменные и параметры. Но это не совсем так. Для объектов хранятся только ссылки на них, в то время как сами объекты находятся в куче. К слову, это уже не совсем так благодаря функции Escape Analysis из Java 6: те объекты, которые являются исключительно локальными и не возвращаются за пределы выполняемого метода также сохраняются в стеке. Это сделано для того, чтобы сборщик мусора впоследствии мог их быстро удалить.
Заключение.
Итак, мы познакомились с тем, как же JVM «под капотом», работает с памятью, выделяемой для создаваемых объектов,где хранятся статические переменные и данные для каждого потока. Жду ваших комментариев и пожеланий. Успехов в программировании!
Основы управления памятью в Java
Общие сведения о управление памятью
Управление памятью — это процесс размещения новых объектов и удаление неиспользуемых объектов, чтобы освободить место для этих новых ассигнований объектов. Традиционным для языков программирование способом управления памятью является ручной. Его сущность является в следующем:
- Для создания объекта в динамической памяти программист явно вызывает команду выделения памяти. Эта команда возвращает указатель на выделенную область памяти, который сохраняется и используется для доступа к ней.
- До тех пор, пока созданный объект нужен для работы программы, программа обращается к нему через ранее сохранённый указатель.
- Когда надобность в объекте проходит, программист явно вызывает команду освобождения памяти, передавая ей указатель на удаляемый объект.
- Ручное управление памятью допускает потенциально возможные две проблемы: висячие ссылки и утечки памяти.
Висячая ссылка — это оставшаяся в использовании ссылка на объект, который уже удалён. После удаления объекта все сохранившиеся в программе ссылки на него становятся «висячими». Память, занимаемая ранее объектом, может быть передана операционной системе и стать недоступной, или быть использована для размещения нового объекта в той же программе.
Утечка памяти — процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих ненужные уже участки памяти.
Все проблемы ручного способа управления памяти в Java решает автоматический сборщик мусора. Но пред ознакомлением со сборщиком мусора, нужно разъяснить понятие кучи(heap).
Куча
В Java все объекты находятся в области памяти под названием куча. Куча создается, когда JVM запускается и может увеличиваться или уменьшаться в размерах во время выполнения приложения. Когда куча становится полной, происходит механизм сборки мусора. Все объекты, которые никогда больше не будут использоватся, очищаются. тем самым освобождая место для новых объектов.
Также нужно обратить внимание, что JVM использует больше памяти, чем занимает куча. Например, для методов Java и стеков потоков выделяется память отдельно от кучи.
Размер кучи зависит от используемой платформы, но, как правило, это где-то между 2 и 128 Кб.
Garbage Collection
Механизм сборки мусора — это процесс освобождения места в куче, для возможности добавления новых объектов.
Объекты создаются посредством оператора new, тем самым присваивая объекту ссылку. Закончив работу с объектом, вы просто перестаете на него ссылаться — достаточно присвоить переменной ссылку на другой объект или значение null либо прекратить выполнение метода, чтобы его локальные переменные завершили свое существование естественным образом. Объекты, ссылки на которые отсутствуют, принято называть мусором (garbage), который будет удален.
Виртуальная машина Java, применяя механизм сборки мусора, гарантирует, что любой объект, обладающий ссылками, остается в памяти — все объекты, которые недостижимы из выполняемого кода ввиду отсутствия ссылок на них, удаляются с высвобождением отведенной для них памяти. Точнее говоря, объект не попадает в сферу действия процесса сборки мусора, если он достижим посредством цепочки ссылок, начиная с корневой (root) ссылки, т. е. ссылки, непосредственно существующей в выполняемом коде.
Память освобождается сборщиком мусора по его собственному «усмотрению» и обычно только в тех случаях, когда для дальнейшей работы программы необходим фрагмент свободной памяти большего размера, нежели тот, который имеется в распоряжении виртуальной машины в данный момент, либо если сборщик мусора «предвидит» потенциальную нехватку памяти в ближайшем будущем. Программа может (часто именно так и происходит) завершить работу, не исчерпав ресурсов свободной памяти или даже не приблизившись к этой черте, и поэтому ей так и не потребуются «услуги» сборщика мусора. Объект считается «более недостижимым», если ни одна из переменных в коде, выполняемом в данный момент, не содержит ссылок на него либо цепочка ссылок, которая могла бы связать объект с некоторой переменной программы, обрывается.
Мусор собирается системой без вашего вмешательства, но это не значит, что процесс не требует внимания вовсе. Необходимость создания и удаления большого количества объектов существенным образом сказывается на производительности приложений, и если быстродействие программы является важным фактором, следует тщательно обдумывать решения, связанные с созданием объектов, — это, в свою очередь, уменьшит и объем мусора, подлежащего утилизации.
Если Вам понравилась статья, проголосуйте за нее
Голосов: 25
Голосовать
2 способа увеличения оперативной памяти
Из-за взаимодействия программного компонента Java с разработанными продуктами могут возникать ошибки, решение которых лежит на плечах пользователя. Оно достигается двумя путями: переустановкой модуля и выделением дополнительной памяти Java. С каждой ситуацией стоит разобраться отдельно.
Зачем увеличивать память Java
Задачу по увеличению Java памяти пользователи ставят перед собой в следующих случаях:
- Не запускается игра Minecraft. Геймер получает сообщение, что для запуска не хватает виртуальной памяти, хотя минимальные требования по оперативке соблюдены.
- Проблема с памятью кучи Java. Написанное серверное приложение не запускается. Для его полноценной работы требуется 512 Мб оперативки на компьютере, но трудности с запуском возникают даже при имеющихся 4 Гб.
Исправить проблему можно двумя способами.
Как выделить память Java
Выделить Джава-модулю больше оперативной памяти возможно через «Панель управления». Способ удобнее рассмотреть на примере проблем с запуском игры Minecraft.
Инструкция:
- Открывается «Панель управления».
- В поиске нужно найти Java-модуль.
- После запуска ПО в шапке выбирается раздел Java.
- В запустившемся окне открывается View.
- Для корректной работы модуля удалите лишние строки, если они есть. Должна остаться только одна, где указана последняя версия ПО. Важно обратить внимание на разрядность.
- Для увеличения памяти производится изменение столбца Runtime Parameters. При этом параметры записываются в следующем виде: -Xincgc-Xmx2048M, где 2048 – 2 Гб выделяемой оперативки. Важно писать без пробелов. В 32-битной ОС рекомендуется выделение 768 Мб.
- Нажимается ОК, ОС перезагружается.
Расшифровка используемых команд:
- Xincgc – освобождает неиспользуемые объекты из памяти;
- Xmx – максимальный объем оперативки;
- Xms – минимальный объем.
Если это не помогло запустить Minecraft, переустановите модуль Java и игру. После удаления очистите реестр с помощью CCleaner.
Увеличение памяти с помощью переменных среды
Увеличить оперативную память в Джаве можно с помощью переменных системной среды. В виртуальной машине прописываются два аргумента, упомянутых ранее: -Xms и -Xmx.
Чтобы система воспринимала написанные аргументы, нужно добавить переменную с названием «_JAVA_OPTIONS».
Если количество памяти, отведенной для работы Java, в два раза меньше имеющейся оперативки, то команды прописываются по следующей инструкции:
- Открываются «Свойства» на ярлыке «Мой компьютер».
- Из левой части выбираются «Дополнительные параметры системы».
- На вкладке «Дополнительно» производится одиночный клик по «Переменные среды».
- Нажимается кнопка «Создать».
- Имя переменной: «_JAVA_OPTIONS», аргументы: «-Xms512m -Xmx1024m».
В примере объем оперативки составлял 1 Гб.
Видео: 3 способа выделить больше памяти Java.
Таким образом в статье рассмотрено два метода увеличения оперативной памяти, выделяемой для работы Java-модуля.
Загрузка…
Что потребляет память в процессе Java?
мы пытаемся исследовать использование памяти процессом Java при умеренной нагрузке.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12663 test 20 0 8378m 6.0g 4492 S 43 8.4 162:29.95 java
Как вы можете видеть, у нас есть резидентная память на 6Gb. Теперь интересная часть заключается в следующем: процесс выполняется с этими параметрами:
- -Xmx2048m
- -Xms2048m
- -XX: NewSize = 512m
- -XX: MaxDirectMemorySize = 256m
- . .. некоторые другие для GC и прочее
Глядя на эти настройки и на фактическое использование памяти, мы сталкиваемся с разницей в том, что мы ожидаем, что этот процесс будет использовать и что он на самом деле использует.
Обычно проблемы с памятью решаются путем анализа дампа кучи, но в этом случае наша память используется где-то за пределами кучи.
Вопросы: Каковы были бы шаги, чтобы попытаться найти причину такого высокого использования памяти? Какие инструменты могут помочь нам определить, что использует память в этом процессе?
РЕДАКТИРОВАТЬ 0
Не похоже, что это проблема, связанная с кучей, поскольку у нас все еще есть достаточно места:
jmap -heap 12663
результаты (отредактировано для экономии места)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 536870912 (512.0MB)
MaxNewSize = 536870912 (512.0MB)
OldSize = 1610612736 (1536. 0MB)
NewRatio = 7
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
New Generation: 45.7% used
Eden Space: 46.3% used
From Space: 41.4% used
To Space: 0.0% used
concurrent mark-sweep generation: 63.7% used
Perm Generation: 82.5% used
РЕДАКТИРОВАТЬ 1
используя pmap, мы можем видеть, что существует довольно много выделений 64 Мб:
pmap -x 12663 | grep rwx | sort -n -k3 | less
результаты в:
... a lot more of these 64Mb chunks
00007f32b8000000 0 65508 65508 rwx-- [ anon ] <- what are these?
00007f32ac000000 0 65512 65512 rwx-- [ anon ]
00007f3268000000 0 65516 65516 rwx-- [ anon ]
00007f3324000000 0 65516 65516 rwx-- [ anon ]
00007f32c0000000 0 65520 65520 rwx-- [ anon ]
00007f3314000000 0 65528 65528 rwx-- [ anon ]
00000000401cf000 0 241904 240980 rwx-- [ anon ] <- Direct memory ?
000000077ae00000 0 2139688 2139048 rwx-- [ anon ] <- Heap ?
Итак, как узнать, что это за куски по 64 Мб? Что их использует? Какие данные в них?
Спасибо
Java Memory Model — urvanov.
ru
Модель памяти Java или Java Memory Model (JMM) описывает поведение программы в многопоточной среде. Она объясняет возможное поведение потоков и то, на что должен опираться программист, разрабатывающий приложение.
В этой статье дальше приведено достаточно большое количество терминов. Думаю, что большая часть из них пригодится вам только на собеседованиях, но представлять общую картину того, что такое Java Memory Model всё-таки полезно.
Java может работать на разных процессорах и разных операционных системах, что приводит к затруднению синхронизации между потоками. Многие современные процессоры имеют несколько ядер, могут выполнять команды не в той последовательности, в которой они записаны, а также компиляторы могут менять последовательность команд для оптимизации.
Неправильно синхронизированные программы могут приводить к неожиданным результатам.
Например, программа использует локальные переменные
r1 и
r2 и общие переменные
A и
B. Первоначально
A == B == 0.
Thread 1 | Thread 2 |
1: r2 = A; | 3: r1 = B; |
2: B = 1; | 4: A = 2; |
Может показаться, что результат r2 == 2 и r1 == 1 невозможен, так как либо инструкция 1 должна быть первой, либо инструкция 3 должна быть первой. Если инструкция 1 будет первой, то она не сможет увидеть число 2, записанное в инструкции 4. Если инструкция 3 будет первой, то она не сможет увидеть результат инструкции 2.
Если какое-то выполнение программы привело бы к такому поведению, то мы бы знали, что инструкция 4 была до инструкции 1, которая была до инструкции 2, которая была до инструкции 3, которая была до инструкции 4, что совершенно абсурдно.
Однако современным компиляторам разрешено переставлять местами инструкции в обоих потоках в тех случаях, когда это не затрагивает исполнение одного потока не учитывая другие потоки. Если инструкция 1 и инструкция 2 поменяются местами, то мы с лёгкостью сможем получит результат
r2 == 2 и
r1 == 1.
Thread 1 | Thread 2 |
B = 1; | r1 = B; |
r2 = A; | A = 2; |
Для некоторых программистов подобное поведение может оказаться ошибочным, но здесь нужно сделать замечание, что этот код неверно синхронизирован:
- у нас есть запись из одного потока;
- мы читаем ту же переменную из другого потока;
- чтение и запись не синхронизированы, что не гарантирует правильный порядок.
Ситуация, описанные в примере выше, называется «состоянием гонки» или Data Race.
Переставлять команды может Just-In-Time компилятор или процессор. Более того, каждое ядро процессора может иметь свой кеш. А значит, у каждого процессора может быть своё значение одной и той же переменнной, что может привести к аналогичным результатам.
Модель памяти описывает, какие значения могут быть считаны в каждый момент программы. Поведение потока в изоляции должно быть таким, каким описано в самом потоке, но значения, считываемые из переменных определяются моделью памяти. Когда мы ссылаемся на это, то мы говорим, что программа подчиняется intra-thread semantic, то есть семантики однопоточного приложения.
Разделяемые переменные
Память, которая может быть совместно использована разными потоками, называется куча (shared memory или heap memory).
Все переменные экземпляров, статические поля, массивы элементов хранятся в куче. Дальше в этой статье я буду называть их всех просто переменными.
Локальные переменные, параметры конструкторов и методов, а также параметры блока
catch никогда не разделяются между потоками.
Два доступа к одной переменной называются конфликтующими, если хотя бы один их доступов меняет значение переменной (другой может как менять, так и считывать текущее значение).
Действия
Inter-thread action (термин такой, не знаю, как перевести, может, межпоточное действие?) — это действие внутри одного потока, которое может повлиять или быть замечено другим потоком. Существует несколько типов inter-thread action:
- Чтение (нормальное, не volatile). Чтение переменной.
- Запись (нормальная, не volatile). Запись переменной.
- volatile read. Чтение volatile переменной.
- volatile write. Запись volatile переменной.
- Lock. Взятие блокировки монитора.
- Unlock. Освобождение блокировки монитора.
- (синтетические) первое и последнее действие в потоке.
- Действия по запуску нового потока или обнаружения остановки потока.
- Внешние действия. Это действия, которые могут быть обнаружены снаружи выполняющегося потока, например, взаимодействия с внешним окружением.
- Thread divergence actions. Действия потока, находящегося в бесконечном цикле без синхронизаций, работы с памятью или внешних действий.
Program order
Program order (лучше не переводить, чтобы не возникло путаницы) — общий порядок потока, выполняющего действия, который отражает порядок, в котором должны быть выполнены все действия с соответствии с семантикой intra-thread semantic потока.
Действия называются sequentially consistent (лучше тоже не переводить), если все действия выполняются в общем порядке, который соответствует program order, а также каждое чтение переменной видит последнее значение, записанное туда до этого в соответствии с порядком выполнения.
Если в программе нет состояния гонки, то все запуски программы будут sequentially consistent.
Synchronization order
Synchronization order (порядок синхронизации, но лучше не переводить) — общий порядок всех действий по синхронизации в выполнении программы.
Действия по синхронизации вводят связь synchronized-with (синхронизировано с):
- Действие освобождения блокировки монитора synchronizes-with все последующие действия по взятию блокировки этого монитора.
- Присвоение значения
volatile переменной synchronizes-with все последующие чтения этой переменной любым потоком. - Действие запуска потока synchronizes-with с первым действием внутри запущенного потока.
- Присвоение значения по умолчанию (0, false, null) каждой переменной synchronizes-with с первым действием каждого потока.
- Последнее действие в потоке synchronizes-with с любым действием других потоков, которые проверяют, что первый поток завершился.
- Если поток 1 прерывает поток 2, то прерывание выполнения потока 2 synchronizes-with с любой точкой, где другой поток (и прерывающий тоже) проверяет, что поток 2 был прерван (
InterruptedException,
Thread.interrupted,
Thread.isInterrupted).
Happens-before
Happens-before («выполняется прежде» или «произошло-до») — отношение порядка между атомарными командами. Оно означает, что вторая команда будет видеть изменения первой команды, и что первая команды выполнилась перед второй. Рекомендую ознакомиться с многопоточностью в Java, перед продолжением чтения.
Happens-before возникает:
- Освобожение монитора happens-before любого последующего взятия блокировки этого монитора.
- Присвоение значение
volatile полю happens-before любого последующего чтения значения этого поля. - Запуск потока happens-before любых действий в запущенном потоке.
- Все действия внутри потока happens-before любого успешного завершения
join() над этим потоком. - Инициализация по умолчанию для любого объекта happens-before любых других действий программы.
Работа с final полями
Все
final поля должны быть инициализированы либо конструкциями инициализации, либо внутри конструктора. Не стоит внутри конструкторов обращаться к другим потокам. Поток увидит ссылку на объект только после полной инициализации, то есть по окончании работы конструктора. Так как
final полям присваивается значение только один раз, то просто не обращайтесь к другим потоком внутри конструкторов и блоков инициализации и проблем возникнуть не должно.
Однако
final поля могут быть изменены через Java Reflection API, чем пользуются, например, десериализаторы. Просто не отдавайте ссылку на объект другим потокам и не читайте значение
final поля до его обновления и всё будет нормально.
Word tearing
Некоторые процессоры не позволяют записывать один байт в ОЗУ, что приводит к проблеме, называемой word tearing. Представьте, что у нас есть массив байт. Один поток записывает первый байт, а второй поток пытается записать значение в рядом стоящий байт. Но если процессор не может записать один байт, а только целое машинное слово, то запись рядом стоящего байта может быть проблематичной. Если просто считать машинное слово, обновить один байт и записать обратно, то мы помешаем другому потоку.
Поэтому при записи разных байт в одном массиве всё же нужно пользоваться синхронизацией. На сколько я знаю, в x86 такой проблемы нет, но всё равно полагаться на это не нужно, здесь нужна синхронизация.
Поделиться:
Управление памятью Java для виртуальной машины Java (JVM)
Получите больше подобных вещей
Зарегистрируйтесь в BETSOL, чтобы узнавать о последних технологических тенденциях, лучших отраслевых практиках и тестах.
Спасибо за подписку.
Что-то пошло не так.
Последнее обновление: 11 декабря 2020 г., автор: Umme Kulsum
Управление памятью 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 . Ясли разделена на три части — Eden Memory и две Survivor Memory .
Важные сведения о детской комнате:
- Большинство вновь созданных объектов находится в пространстве Eden Memory
- Когда пространство Эдема заполняется объектами, выполняется Малый сборщик мусора, и все выжившие объекты перемещаются в одно из оставшихся в живых пространств
- Minor GC также проверяет выжившие объекты и перемещает их в другое место выживших. Так что за один раз одно из оставшихся в живых ячеек всегда пусто
- Объекты, которые пережили много циклов сборки мусора, перемещаются в пространство памяти старого поколения.Обычно это делается путем установки порогового значения возраста объектов питомника, прежде чем они станут подходящими для перехода к старому поколению
Когда старое поколение заполняется, там собирается мусор, и этот процесс называется старой сборкой. Память старого поколения содержит объекты, которые являются долгоживущими и выжили после многих циклов Minor GC. Обычно сборка мусора выполняется в памяти старого поколения, когда она заполнена. Сборка мусора старого поколения называется Major GC и обычно занимает больше времени.Причина создания детской заключается в том, что большинство объектов временны и недолговечны. Молодая коллекция предназначена для того, чтобы быстро находить недавно выделенные объекты, которые еще живы, и перемещать их подальше от детской. Как правило, молодая коллекция освобождает заданный объем памяти намного быстрее, чем старая коллекция или сборка мусора для кучи одного поколения (куча без питомника).
Последние выпуски включают часть питомника, называемую , хранилище , и она зарезервирована.Область хранения содержит самые недавно выделенные объекты в питомнике и не подлежит сборке мусора до следующего молодого поколения. Это предотвращает повышение объектов только потому, что они были выделены непосредственно перед запуском молодой коллекции.
Модели памяти Java
Постоянное поколение (заменено на Metaspace с Java 8)
Permanent Generation или «Perm Gen» содержит метаданные приложения, необходимые JVM для описания классов и методов, используемых в приложении.Perm Gen заполняется JVM во время выполнения на основе классов, используемых приложением. Perm Gen также содержит классы и методы библиотеки Java SE. Объекты Perm Gen собираются мусором при полной сборке мусора.
Metaspace
В 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) сохраняет скомпилированный код в области, называемой кешем кода.Это специальная куча, в которой хранится скомпилированный код. Эта область очищается, если ее размер превышает пороговое значение и эти объекты не перемещаются сборщиком мусора.
Некоторые из проблем с производительностью и проблема того, что компилятор не включается повторно, были решены в Java 8, и одним из решений, позволяющих избежать этих проблем в Java 7, является увеличение размера кэша кода до такой степени, что никогда не будет достиг.
Область метода
Method Area является частью пространства Perm Gen и используется для хранения структуры классов (постоянных времени выполнения и статических переменных) и кода для методов и конструкторов.
Пул памяти
Пулы памяти создаются менеджерами памяти JVM для создания пула неизменяемых объектов. Пул памяти может принадлежать Heap или Perm Gen, в зависимости от реализации диспетчера памяти JVM.
Пул констант времени выполнения
Пул констант времени выполнения — это представление пула констант в классе во время выполнения для каждого класса. Он содержит константы времени выполнения и статические методы класса. Пул констант времени выполнения является частью области метода.
Стек памяти Java
Стековая память Java используется для выполнения потока.Они содержат кратковременные значения для конкретных методов и ссылки на другие объекты в куче, на которые ссылается метод.
Переключатели памяти Java Heap
Java предоставляет множество переключателей памяти, которые мы можем использовать для установки размеров памяти и их соотношений. Некоторые из наиболее часто используемых переключателей памяти:
Коммутатор VM | Описание коммутатора VM |
— Xms | Для установки начального размера кучи при запуске JVM |
-Xmx | Для установки максимального размера кучи |
-Xmn | Для установки размера молодого поколения, остальное пространство отводится старому поколению |
-XX: PermGen | Для установки начального размера постоянной памяти поколения |
-XX: MaxPermGen | Для установки максимального размера Perm Gen |
-XX: Коэффициент выживаемости | Для обеспечения соотношения пространства Eden, например, если размер молодого поколения составляет 10 м, а переключатель VM равен –XX: SurvivorRatio = 2, тогда 5 м будут зарезервированы для пространства Eden и 2. По 5 м для обоих мест для выживших. Значение по умолчанию — 8 |
-XX: NewRatio | Для обеспечения соотношения размеров старого / нового поколения. Значение по умолчанию — 2 |
Таблица 1.1
Сборка мусора
Сборка мусора — это процесс освобождения места в куче для размещения новых объектов. Одна из лучших функций Java — автоматическая сборка мусора. Сборщик мусора — это программа, работающая в фоновом режиме, которая просматривает все объекты в памяти и обнаруживает объекты, на которые не ссылается какая-либо часть программы.Все эти объекты, на которые нет ссылок, удаляются, а пространство освобождается для выделения другим объектам. Один из основных способов сборки мусора состоит из трех этапов:
- Маркировка : это первый шаг, на котором сборщик мусора определяет, какие объекты используются, а какие не используются.
- Обычное удаление : Сборщик мусора удаляет неиспользуемые объекты и освобождает свободное пространство для выделения другим объектам
- Удаление с уплотнением : Для повышения производительности после удаления неиспользуемых объектов все уцелевшие объекты можно переместить вместе. Это увеличит производительность выделения памяти более новым объектам
Mark and Sweep Модель сборки мусора
JVM использует модель сборки мусора mark and sweep для выполнения сборки мусора всей кучи. Сборка мусора с меткой и очисткой состоит из двух фаз: фазы метки и фазы очистки.
Во время фазы отметки все объекты, доступные из потоков Java, собственных обработчиков и других корневых источников, помечаются как живые, а также объекты, доступные из этих объектов и т. Д.Этот процесс идентифицирует и отмечает все объекты, которые все еще используются, а остальное можно считать мусором.
Во время фазы развертки куча просматривается, чтобы найти промежутки между живыми объектами. Эти пробелы записываются в свободный список и становятся доступными для размещения новых объектов.
Типы сборки мусора Java
Существует пять типов типов сборки мусора, которые мы можем использовать в наших приложениях. Нам просто нужно использовать переключатель JVM, чтобы включить стратегию сборки мусора для приложения.
Последовательный GC (-XX: + UseSerialGC) : Последовательный GC использует простой подход mark-sweep-compact для сборки мусора молодого и старого поколений, то есть Minor и Major GC
Для включения последовательного сборщика используйте:
-XX: + UseSerialGC
Параллельный сборщик мусора (-XX: + UseParallelGC) : Параллельный сборщик мусора такой же, как последовательный сборщик мусора, за исключением того, что он порождает N потоков для сборки мусора молодого поколения, где N — количество ядер ЦП в системе. Мы можем контролировать количество потоков с помощью параметра –XX: ParallelGCThreads = n JVM.Это коллектор JVM по умолчанию в JDK 8
.
Чтобы включить параллельный сборщик мусора, используйте:
-XX: + UseParallelGC
Старый параллельный сборщик мусора (-XX: + UseParallelOldGC) : то же самое, что и параллельный сборщик мусора, за исключением того, что он использует несколько потоков для сборки мусора как молодого, так и старого поколения
Чтобы включить параллельный OLDGC, используйте:
-XX: + UseParallelOldGC
Коллектор параллельного сканирования меток (CMS) (-XX: + UseConcMarkSweepGC) : CMS также называется сборщиком параллельных периодов с низкой паузой. Он занимается сборкой мусора для старого поколения. Сборщик CMS пытается минимизировать паузы из-за сборки мусора, выполняя большую часть работы по сборке мусора одновременно в потоках приложения. Сборщик CMS на молодом поколении использует тот же алгоритм, что и параллельный сборщик. Этот сборщик мусора подходит для отзывчивых приложений, где мы не можем позволить себе более длительные паузы. Мы можем ограничить количество потоков в сборщике CMS, используя –XX: ParallelCMSThreads = n JVM option
Для включения CMS Collector используйте:
-XX: + UseConcMarkSweepGC
Сборщик мусора G1 (-XX: + UseG1GC) : Сборщик мусора сначала или G1 доступен в Java 7, и его долгосрочная цель — заменить сборщик CMS.Сборщик G1 — это параллельный, параллельный и постепенно компактный сборщик мусора с малой паузой. Первый сборщик мусора не работает, как другие сборщики, и нет концепции пространства молодого и старого поколения. Он делит пространство кучи на несколько областей кучи одинакового размера. Когда вызывается сборщик мусора, он сначала собирает область с меньшим объемом оперативных данных, следовательно, «сначала мусор».
Для включения коллектора G1 используйте:
-XX: + Использовать G1GC
G1 планируется в качестве долгосрочной замены для Concurrent Mark-Sweep Collector (CMS).Сравнивая G1 с CMS, есть различия, которые делают G1 лучшим решением. Одно отличие состоит в том, что G1 — это уплотнительный коллектор. G1 достаточно сжимается, чтобы полностью избежать использования детализированных списков свободных мест для распределения, и вместо этого полагается на регионы. Это значительно упрощает части сборщика и в основном устраняет потенциальные проблемы фрагментации. Кроме того, G1 предлагает более предсказуемые паузы при сборке мусора, чем сборщик CMS, и позволяет пользователям указывать желаемые цели паузы.
В Java 8 сборщик G1 поставляется с удивительной оптимизацией, известной как String Deduplication .Он позволяет GC идентифицировать строки, которые имеют несколько экземпляров в куче, и изменять их так, чтобы они указывали на один и тот же внутренний массив char [], чтобы не было нескольких копий в куче. Его можно включить с помощью -XX: + UseStringDeduplication JVM аргумент.
G1 — это сборщик мусора по умолчанию в JDK 9.
Чемодан
Более 50% кучи Java занято оперативными данными.
Скорость размещения или продвижения объекта значительно различается.
Нежелательные длительные паузы сборки или уплотнения мусора (от 0,5 до 1 секунды)
Мониторинг использования памяти и активности ГХ
Нехватка памяти часто является причиной нестабильности и зависания приложений Java. Следовательно, нам необходимо отслеживать влияние сборки мусора на время отклика и использование памяти, чтобы обеспечить как стабильность, так и производительность. Однако мониторинга использования памяти и времени сборки мусора недостаточно, поскольку эти два элемента сами по себе не говорят нам, влияет ли сборка мусора на время отклика приложения.Только приостановка сборки мусора напрямую влияет на время отклика, и сборщик мусора также может работать одновременно с приложением. Следовательно, нам необходимо соотнести приостановки, вызванные сборкой мусора, со временем отклика приложения. Исходя из этого, нам необходимо отслеживать следующее:
- Использование различных пулов памяти (Eden, Survivor и старое поколение). Нехватка памяти — главная причина повышенной активности сборщика мусора
- Если общее использование памяти постоянно увеличивается, несмотря на сборку мусора, возникает утечка памяти, которая неизбежно приведет к нехватке памяти .В этом случае необходим анализ кучи памяти
- Количество коллекций молодого поколения предоставляет информацию о скорости оттока (скорость распределения объектов). Чем выше число, тем больше объектов размещается. Большое количество молодых коллекций может быть причиной проблемы времени отклика и растущего старшего поколения (потому что молодое поколение больше не может справляться с количеством объектов)
- Если использование старого поколения сильно колеблется, не увеличиваясь после сборки мусора, то объекты без надобности копируются из молодого поколения в старое. Этому есть три возможных причины: молодое поколение слишком мало, высокий уровень оттока или слишком много транзакционной памяти.
- Высокая активность сборщика мусора обычно отрицательно сказывается на использовании ЦП. Однако только приостановки (остановки событий) имеют прямое влияние на время отклика. Вопреки распространенному мнению, приостановка не ограничивается крупными сборщиками мусора. Поэтому важно отслеживать приостановки в зависимости от времени отклика приложения
.
jstat
Утилита jstat использует встроенные инструменты в Java HotSpot VM для предоставления информации о производительности и потреблении ресурсов запущенных приложений.Этот инструмент можно использовать при диагностике проблем с производительностью и, в частности, проблем, связанных с изменением размера кучи и сборкой мусора. Утилита jstat не требует запуска виртуальной машины с какими-либо специальными параметрами. Встроенные инструменты в виртуальной машине Java HotSpot включены по умолчанию. Эта утилита включена в загрузку JDK для всех операционных систем. Утилита jstat использует идентификатор виртуальной машины (VMID) для идентификации целевого процесса.
Использование команды jstat с опцией gc для определения использования памяти кучи JVM.
Рис 1.3
S0C | Текущая емкость оставшегося места 0 (КБ) |
S1C | Текущая емкость выжившего пространства 1 (КБ) |
S0U | Использование свободного места 0 (КБ) |
S1U | Использование помещения для выживших 1 (КБ) |
EC | Текущая емкость пространства Eden (КБ) |
ЕС | Использование пространства Eden (КБ) |
OC | Текущая емкость старого пространства (КБ) |
ОУ | Использование старого пространства (КБ) |
MC | Емкость Metasapce (КБ) |
MU | Использование Metaspace (КБ) |
CCSC | Емкость сжатого класса (КБ) |
CCSU | Используемое сжатое пространство классов (КБ) |
YGC | Количество событий сборки мусора молодого поколения |
YGCT | Время сборки мусора молодого поколения |
FGC | Количество событий полного GC |
FGCT | Время полной сборки мусора |
GCT | Общее время сборки мусора |
Таблица 1. 2
jmap
Утилита jmap выводит статистику, связанную с памятью, для работающей виртуальной машины или файла ядра. JDK 8 представил Java Mission Control, Java Flight Recorder и утилиту jcmd для диагностики проблем с JVM и приложениями Java. Рекомендуется использовать последнюю версию утилиты jcmd вместо утилиты jmap для расширенной диагностики и снижения накладных расходов на производительность.
Параметр –heap может использоваться для получения следующей информации о куче Java:
- Информация, относящаяся к алгоритму GC, включая имя алгоритма GC (например, параллельный GC) и детали алгоритма (например, количество потоков для параллельного GC).
- Конфигурация кучи, которая могла быть указана как параметры командной строки или выбрана виртуальной машиной в зависимости от конфигурации машины.
- Сводка по использованию кучи: для каждого поколения (область кучи) средство выводит общую емкость кучи, используемую память и доступную свободную память. Если поколение организовано как набор пространств (например, новое поколение), то включается сводка по размеру памяти для конкретного пространства.
Рис 1.4
смд
Утилита jcmd используется для отправки запросов диагностических команд в JVM, где эти запросы полезны для управления записями полета Java, устранения неполадок и диагностики приложений JVM и Java. Он должен использоваться на том же компьютере, на котором запущена виртуальная машина Java, и иметь такие же эффективные идентификаторы пользователя и группы, которые использовались для запуска JVM.
Дамп кучи (дамп hprof) можно создать с помощью следующей команды:
jcmd
Приведенная выше команда аналогична использованию
jmap –dump: file =
Но рекомендуется использовать jcmd .
джат
Инструмент jhat предоставляет удобные средства для просмотра топологии объекта в моментальном снимке кучи. Этот инструмент заменяет инструмент анализа кучи (HAT). Инструмент анализирует дамп кучи в двоичном формате (например, дамп кучи, созданный jcmd ).Эта утилита может помочь отладить непреднамеренное объектное отношение . Этот термин используется для описания объекта, который больше не нужен, но остается активным благодаря ссылкам через некоторый путь из корневого набора. Это может произойти, например, если непреднамеренная статическая ссылка на объект остается после того, как объект больше не нужен, если наблюдатель или слушатель не может отменить регистрацию от своего субъекта, когда он больше не нужен, или если поток, который ссылается на объект не завершается, когда должен.Непреднамеренное объектное отношение — это эквивалент утечки памяти в языке Java.
Мы можем проанализировать дамп кучи с помощью jhat с помощью следующей команды
jhat
Эта команда считывает файл . hprof и запускает сервер на порту 7000.
Рис 1.5
Когда мы подключены к серверу по http: // localhost: 7000, мы можем выполнить стандартный запрос или создать язык объектных запросов (OQL). По умолчанию отображается запрос «Все классы».На этой странице по умолчанию отображаются все классы, присутствующие в куче, за исключением классов платформы. Этот список отсортирован по полному имени класса и разбит по пакетам. Щелкните имя класса, чтобы перейти к запросу класса. Второй вариант этого запроса включает классы платформы. Классы платформы включают классы, полные имена которых начинаются с таких префиксов, как java, sun или javax.swing. С другой стороны, запрос класса отображает информацию о классе. Сюда входят его суперкласс, любые подклассы, члены данных экземпляра и члены статических данных.На этой странице вы можете перейти к любому из классов, на которые есть ссылки, или вы можете перейти к запросу экземпляра. Запрос экземпляра отображает все экземпляры данного класса.
HPROF
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
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
Имеется четыре вкладки с подробными сведениями о приложении: Обзор, Монитор, Потоки и Сэмплер.
Вкладка Overview содержит основную информацию о запущенном приложении. Основной класс, аргументы командной строки, аргументы JVM, PID, системные свойства и любые сохраненные данные, такие как дампы потоков или дампы кучи, доступны на вкладке обзора.
Интересная вкладка — это вкладка Монитор . На этой вкладке отображается использование ЦП и памяти приложением. В этом представлении есть четыре графика.
Рис 1,7
Первый график отображает использование ЦП и ЦП сборщика мусора. Ось X показывает отметку времени в зависимости от процента использования.
Второй график в правом верхнем углу отображает пространство кучи и пространство Perm Gen или Metaspace. Он также показывает максимальный размер кучи памяти, сколько он используется приложением и сколько доступно для использования.Этот график особенно полезен при анализе приложений, в которых возникают ошибки java.lang.OutOfMemoryError: пространства кучи Java. Когда приложение выполняет задание с интенсивным использованием памяти, используемая куча (обозначенная синим цветом на графике) всегда должна быть меньше размера кучи (представленной оранжевым цветом на графике). Когда размер используемой кучи почти такой же, как размер кучи, или когда в системе больше нет места для выделения / расширения размера кучи, а используемая куча продолжает увеличиваться, мы можем ожидать ошибки кучи.Более подробную информацию о куче можно получить, взяв «Дамп кучи». При возникновении ошибки нехватки памяти дамп кучи можно получить, добавив текущие параметры виртуальной машины:
-XX: + HeapDumpOnOutOfMemoryError –XX: HeapDumpPath = [путь к файлу]
Это позволит создать файл . hprof по указанному пути.
Рис 1.8
На рис. 1.8 показан дамп кучи для одного из приложений. На вкладке сводки отображается некоторая основная информация, такая как общее количество классов, общее количество экземпляров, загрузчики классов, корни сборки мусора и сведения о среде, в которой выполняется приложение.Анализ на рис. 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 .Когда мы открываем эту вкладку изначально, она не содержит никакой информации. Прежде чем увидеть информацию, мы должны запустить один из видов выборки / профилирования. Начнем с выборки процессора. После нажатия кнопки «CPU» результаты выборки CPU отображаются в таблице.
Рис. 1.11
Из рис. 1.11 видно, что метод doRun () занимает 54,8% процессорного времени. Мы также видим, что getNextEvent () и readAvailableBlocking () — это два следующих метода, которые потребляют больше процессорного времени.
Следующая выборка — это выборка из памяти. Приложение будет заморожено во время выборки до получения результатов. Из рис. 1.12 мы можем сделать вывод, что приложение хранит массивы Object , int, и char .
В обоих типах выборки мы можем сохранить результаты для дальнейшего использования в файл. Например, образцы можно брать несколько раз через равные промежутки времени, и результаты можно сравнивать. Это может помочь нам улучшить приложение, чтобы использовать меньше ЦП и памяти.Наконец, задача разработчика — изучить эти области и улучшить код.
Настройка сборки мусора Java
Настройка сборки мусора Java должна быть последним вариантом, который мы используем для увеличения пропускной способности приложения, и только тогда, когда мы видим падение производительности из-за более длительного GC, вызывающего тайм-ауты приложения.
Если мы обнаружим ошибку java.lang.OutOfMemoryError: PermGen space , попробуйте отслеживать и увеличивать пространство памяти Perm Gen, используя параметры JVM –XX: PermGen и –XX: MaxPermGen .Мы не видим этой ошибки в случае Java 8 и выше. Если мы видим много операций с полным сборщиком мусора, то мы должны попытаться увеличить объем памяти старого поколения. В целом настройка сборки мусора требует много усилий и времени, и для этого нет жесткого правила. Нам нужно попробовать разные варианты и сравнить их, чтобы найти лучший, подходящий для нашего приложения.
Некоторые решения для повышения производительности:
- Отбор проб / профилирование прикладного программного обеспечения
- Настройка сервера и JVM
- Правое железо и ОС
- Улучшение кода в соответствии с поведением приложения и результатами выборки (легче сказать, чем сделать!)
- Правильное использование виртуальной машины Java (с оптимальными параметрами JVM)
- -XX: + UseParallelGC, если есть мультипроцессоры
Еще несколько полезных советов, о которых следует помнить:
- Если у нас нет проблем с паузами, попробуйте предоставить JVM
- Установка для –Xms и –Xmx одного и того же значения
- Обязательно увеличивайте объем памяти по мере увеличения количества процессоров, так как выделения могут быть распараллелены
- Не забудьте настроить Perm Gen
- Минимизировать использование синхронизаций
- Используйте многопоточность, если это приносит пользу, и помните о накладных расходах потоков. Кроме того, убедитесь, что он работает одинаково в разных средах.
- Избегайте преждевременного создания объекта. Создание должно быть близко к месту фактического использования. Это основная концепция, которую мы склонны упускать из виду.
- обычно медленнее сервлетов
- StringBuilder вместо строкового concat
- Используйте примитивы и избегайте объектов. (длинный вместо Long)
- По возможности используйте объекты повторно и избегайте создания ненужных объектов
- equals () стоит дорого, если мы проверяем пустую строку.Вместо этого используйте свойство длины.
- «==» быстрее, чем equals ()
- n + = 5 быстрее, чем n = n + 5. В первом случае генерируется меньше байтовых кодов
- Периодически очищать сеансы гибернации
- Выполнять массовые обновления и удаления
как можно больше памяти
JSP
Создание журналов для GC
Журнал сборщика мусора или gc.log — это текстовый файл, в котором хранятся все события очистки памяти JVM: MinorGC, MajorGC e FullGC.
Запустите JVM с указанными ниже параметрами, чтобы создать gc.журнал:
до Явы 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.ru / article / java-performance-tuning
http://www.journaldev.com/2856/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.andresteingress.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, и многое другое здесь.
Эту статью написали Акшай А. и Абхиджит Аркалгуд.
Связанные
Разница между JVM, JRE и JDK
Если у вас есть несколько лет опыта работы в экосистеме Java, и вы заинтересованы в том, чтобы поделиться этим опытом с сообществом (и, конечно, получать деньги за свою работу), посмотрите на страницу «Напишите для нас».Ваше здоровье. Евгений
1. Обзор
В этой статье мы обсудим различия между JVM, JRE и JDK, рассмотрев их компоненты и способы использования.
2. JVM
Виртуальная машина Java (JVM) — это реализация виртуальной машины, которая выполняет программу Java.
JVM сначала интерпретирует байт-код. Затем он сохраняет информацию о классе в области памяти. Наконец, он выполняет байт-код, сгенерированный компилятором java.
Это абстрактная вычислительная машина со своим собственным набором инструкций, которая управляет различными областями памяти во время выполнения.
Компоненты JVM:
- Погрузчики класса
- Области данных времени выполнения
- Execution Engine
2.1. Погрузчики класса
Первоначальные задачи JVM включают загрузку, проверку и связывание байт-кода. Загрузчики классов справляются с этими задачами.
У нас есть подробная статья, посвященная загрузчикам классов.
2.2. Области данных времени выполнения
JVM определяет различные области памяти для выполнения программы Java. Они используются во время выполнения и известны как области данных времени выполнения. Некоторые из этих областей создаются при запуске JVM и уничтожаются при выходе из JVM, а некоторые создаются при создании потока и уничтожаются при выходе из потока.
Рассмотрим эти области по порядку:
Область метода
По сути, область метода аналогична области хранения скомпилированного кода.В нем хранятся такие структуры, как пул констант времени выполнения, данные полей и методов, код методов и конструкторов, а также полные имена классов. JVM хранит эту структуру для каждого класса.
Область метода, также известная как постоянное пространство генерации (PermGen), создается при запуске JVM. Память для этой области не обязательно должна быть непрерывной. Все потоки JVM совместно используют эту область памяти.
Область кучи
JVM выделяет память для всех экземпляров классов и массивов из этой области.
Сборщик мусора (GC) освобождает динамическую память для объектов. По сути, сборщик мусора имеет три фазы для освобождения памяти от объектов, а именно. два второстепенных сборщика мусора и один основной сборщик мусора.
Память кучи состоит из трех частей:
- Eden Space — это часть пространства Young Generation. Когда мы создаем объект, JVM выделяет память из этого пространства
- Survivor Space — это тоже часть пространства Young Generation. Пространство выжившего содержит существующие объекты, которые пережили второстепенные фазы GC GC
- арендуемое пространство — это также известно как пространство старого поколения.В нем хранятся давно сохранившиеся предметы Как правило, для объектов Young Generation устанавливается порог, и когда этот порог достигается, эти объекты перемещаются в постоянное пространство.
.
JVM создает область кучи при запуске. Все потоки JVM разделяют эту область. Память для области кучи не обязательно должна быть непрерывной.
Площадь стека
Хранит данные в виде фреймов, и в каждом фрейме хранятся локальные переменные, частичные результаты и вызовы вложенных методов.JVM создает область стека всякий раз, когда создает новый поток. Эта область является частной для каждого потока.
Каждая запись в стеке называется кадром стека или записью активации. Каждый фрейм состоит из трех частей:
- Массив локальных переменных — содержит все локальные переменные и параметры метода
- Стек операндов — используется как рабочая область для хранения промежуточных результатов вычислений
- Данные кадра — используются для хранения частичных результатов, возвращаемых значений для методов и ссылки на таблицу Exception , которая предоставляет соответствующую информацию о блоке перехвата в случае исключений
Память для стека JVM не обязательно должна быть непрерывной.
Регистры ПК
Каждый поток JVM имеет отдельный регистр ПК, в котором хранится адрес выполняемой в данный момент инструкции. Если текущая выполняемая инструкция является частью собственного метода, тогда это значение не определено.
Стеки собственных методов
Собственные методы — это методы, написанные на языках, отличных от Java.
JVM предоставляет возможности для вызова этих собственных методов. Стеки собственных методов также известны как «стеки C».Они хранят информацию о собственном методе. Когда собственные методы компилируются в машинные коды, они обычно используют стек собственных методов для отслеживания своего состояния.
JVM создает эти стеки всякий раз, когда создает новый поток. Таким образом, потоки JVM не разделяют эту область.
2.3. Двигатель исполнения
Механизм выполнения выполняет инструкции, используя информацию, имеющуюся в областях памяти. Он состоит из трех частей:
Переводчик
Как только загрузчики классов загружают и проверяют байт-код, интерпретатор выполняет его построчно. Это выполнение довольно медленное. Недостатком интерпретатора является то, что когда один метод вызывается несколько раз, каждый раз требуется новая интерпретация.
Однако JVM использует JIT-компилятор для смягчения этого недостатка.
Компилятор Just-In-Time (JIT)
Компилятор
JIT компилирует байт-код часто вызываемых методов в машинный код во время выполнения. Следовательно, он отвечает за оптимизацию программ Java.
JVM автоматически отслеживает, какие методы выполняются.Как только метод становится пригодным для JIT-компиляции, он планируется для компиляции в машинный код. Этот метод известен как горячий метод. Эта компиляция в машинный код происходит в отдельном потоке JVM.
В результате он не прерывает выполнение текущей программы. После компиляции в машинный код он работает быстрее.
Сборщик мусора
Java заботится об управлении памятью с помощью сборки мусора. Это процесс просмотра памяти кучи, определения того, какие объекты используются, а какие нет, и, наконец, удаления неиспользуемых объектов.
GC — это поток демона. Его можно вызвать явно с помощью системы . gc () , однако он не будет выполнен немедленно, и JVM решит, когда вызывать сборщик мусора.
2.4. Собственный интерфейс Java
Он действует как интерфейс между кодом Java и собственными (C / C ++) библиотеками.
Существуют ситуации, в которых сама по себе Java не отвечает потребностям вашего приложения, например, реализация платформенно-зависимой функции.
В этих случаях мы можем использовать JNI, чтобы разрешить выполнение кода, запущенного в JVM. И наоборот, он позволяет собственным методам вызывать код, выполняемый в JVM.
2,5. Собственные библиотеки
Это библиотеки для конкретной платформы, которые содержат реализацию собственных методов.
3. JRE
Среда выполнения Java (JRE) — это набор программных компонентов, используемых для запуска приложений Java.
Основные компоненты JRE включают:
- Реализация виртуальной машины Java (JVM)
- Классы, необходимые для запуска программ Java
- Файлы свойств
Мы обсуждали JVM в предыдущем разделе.Здесь мы сосредоточимся на основных классах и файлах поддержки.
3.1. Классы начальной загрузки
Мы найдем классы начальной загрузки в jre / lib / . Этот путь также известен как путь к классам начальной загрузки. Включает:
- Классы времени выполнения в rt.jar
- Классы интернационализации в i18n.jar
- Классы преобразования символов в charsets.jar
- Прочие
Bootstrap ClassLoader загружает эти классы при запуске JVM.
3.2. Классы расширения
Мы можем найти классы расширений в jre / lib / extn / , который действует как каталог для расширений платформы Java. Этот путь также известен как путь к классам расширения.
Он содержит библиотеки времени выполнения JavaFX в jfxrt.jar и данные локали для пакетов java.text и java.util в localedata.jar . Пользователи также могут добавлять в этот каталог собственные банки.
3.3. Настройки свойств
Платформа
Java использует эти настройки свойств для поддержания своей конфигурации. В зависимости от их использования они расположены в разных папках внутри / jre / lib / . К ним относятся:
- Конфигурации календаря в calendar.properties
- Конфигурации журналов в logging.properties
- Сетевые конфигурации в net.properties
- Свойства развертывания в / jre / lib / deploy /
- Свойства управления в / jre / lib / management /
3. 4. Прочие файлы
Помимо вышеупомянутых файлов и классов, JRE также содержит файлы для других целей:
- Управление безопасностью в jre / lib / security
- Каталог для размещения классов поддержки для апплетов в jre / lib / applet
- Файлы, связанные со шрифтами в jre / lib / fonts и другие
4. JDK
Java Development Kit (JDK) предоставляет среду и инструменты для разработки, компиляции, отладки и выполнения программы Java.
Основные компоненты JDK включают:
Мы обсуждали JRE в предыдущем разделе.
Теперь мы сосредоточимся на различных инструментах разработки. Давайте классифицируем эти инструменты в зависимости от их использования:
4.1. Основные инструменты
Эти инструменты закладывают основу JDK и используются для создания и построения приложений Java. Среди этих инструментов мы можем найти утилиты для компиляции, отладки, архивирования, создания документации Javadoc и т. Д.
Сюда входят:
- javac — считывает определения классов и интерфейсов и компилирует их в файлы классов
- java — запускает приложение Java
- javadoc — генерирует HTML-страницы документации API из исходных файлов Java
- apt — находит и выполняет обработчики аннотаций на основе аннотаций, присутствующих в наборе указанных исходных файлов
- appletviewer — позволяет запускать Java-апплеты без веб-браузера
- jar — упаковывает Java-апплеты или приложения в единый архив
- jdb — инструмент командной строки для отладки, используемый для поиска и исправления ошибок в приложениях Java
- javah — создает заголовочные файлы C и исходные файлы из класса Java
- javap — разбирает файлы классов и отображает информацию о полях, конструкторах и методах, присутствующих в файле класса
- extcheck — обнаруживает конфликты версий между целевым файлом архива Java (JAR) и установленными в данный момент файлами JAR расширений
4.
2. Средства безопасности
Сюда входят инструменты управления ключами и сертификатами, которые используются для управления хранилищами ключей Java.
Хранилище ключей Java — это контейнер для сертификатов авторизации или сертификатов открытых ключей. Следовательно, он часто используется приложениями на основе Java для шифрования, аутентификации и обслуживания по HTTPS.
Кроме того, они помогают устанавливать политики безопасности в нашей системе и создавать приложения, которые могут работать в рамках этих политик в производственной среде.К ним относятся:
- keytool — помогает в управлении записями хранилища ключей, а именно криптографическими ключами и сертификатами
- jarsigner — создает файлы JAR с цифровой подписью, используя информацию о хранилище ключей
- policytool — позволяет нам управлять файлами конфигурации внешней политики, которые определяют политику безопасности установки.
Некоторые инструменты безопасности также помогают в управлении билетами Kerberos.
Kerberos — это протокол сетевой аутентификации.
Он работает на основе билетов, чтобы позволить узлам, обменивающимся данными по незащищенной сети, безопасно подтверждать свою личность друг другу:
- kinit — , используемый для получения и кэширования билетов выдачи билетов Kerberos
- ktab — управляет именами принципов и парами ключей в таблице ключей
- klist — отображает записи в локальном кэше учетных данных и таблице ключей
4.3. Инструмент интернационализации
Интернационализация — это процесс разработки приложения, позволяющего адаптировать его к различным языкам и регионам без инженерных изменений.
Для этой цели JDK предоставляет native2ascii. Этот инструмент преобразует файл с символами, поддерживаемыми JRE, в файлы, закодированные в escape-последовательности ASCII или Unicode.
4.4. Инструменты удаленного вызова методов (RMI)
Инструменты RMI позволяют осуществлять удаленную связь между приложениями Java, тем самым предоставляя возможности для разработки распределенных приложений.
RMI позволяет объекту, работающему в одной JVM, вызывать методы объекта, работающего в другой JVM.Эти инструменты включают:
- rmic — генерирует классы-заглушки, скелеты и связки для удаленных объектов с помощью протокола удаленного метода Java (JRMP) или межсферного протокола Интернета (IIOP).
- rmiregistry — создает и запускает реестр удаленных объектов
- rmid — s запускает демон системы активации. Это позволяет регистрировать и активировать объекты в виртуальной машине Java
- serialver — возвращает UID серийной версии для указанных классов
.
4.
5. Инструменты Java IDL и RMI-IIOP
Язык определения интерфейса Java (IDL) добавляет в платформу Java возможность общей объектно-ориентированной архитектуры брокера запросов (CORBA).
Эти инструменты позволяют распределенным веб-приложениям Java вызывать операции с удаленными сетевыми службами с использованием стандартной отраслевой группы управления объектами (OMG) — IDL.
Аналогично, мы могли бы использовать протокол Internet InterORB (IIOP).
RMI-IIOP, то есть RMI поверх IIOP, позволяет программировать серверы и приложения CORBA через RMI API.Таким образом обеспечивается соединение между двумя приложениями, написанными на любом CORBA-совместимом языке, через протокол Internet InterORB (IIOP).
Эти инструменты включают:
- tnameserv — временная служба именования, которая предоставляет древовидный каталог для ссылок на объекты
- idlj — Компилятор IDL-to-Java для генерации привязок Java для указанного файла IDL
- orbd — позволяет клиентам прозрачно находить и вызывать постоянные объекты на сервере в среде CORBA
- servertool — предоставляет интерфейс командной строки для регистрации или отмены регистрации постоянного сервера с помощью ORB Daemon ( orbd ), запуска и выключения постоянного сервера, зарегистрированного в ORB Daemon, и т. Д.
4.6. Инструменты развертывания Java
Эти инструменты помогают в развертывании приложений и апплетов Java в Интернете. В их числе:
- pack200 — преобразует файл JAR в файл pack200 с помощью компрессора Java gzip
- unpack200 — преобразует файл pack200 в файл JAR
4.7. Инструмент подключаемого модуля Java
JDK предоставляет нам HTML-конвертер . Кроме того, он используется вместе с подключаемым модулем Java.
С одной стороны, подключаемый модуль Java устанавливает связь между популярными браузерами и платформой Java. В результате этого соединения апплеты на веб-сайте могут работать в браузере.
С другой стороны, htmlconverter — это утилита для преобразования HTML-страницы, содержащей апплеты, в формат для подключаемого модуля Java.
4.8. Инструмент Java Web Start
JDK приносит челюстей. Мы можем использовать его вместе с Java Web Start.
Этот инструмент позволяет загружать и запускать приложения Java одним щелчком мыши в браузере. Следовательно, нет необходимости запускать какой-либо процесс установки.
4.9. Инструменты мониторинга и управления
Это отличные инструменты, которые мы можем использовать для мониторинга производительности JVM и потребления ресурсов. Вот некоторые из них::
- jconsole — предоставляет графическую консоль, которая позволяет отслеживать и управлять приложениями Java.
- jps — перечисляет оснащенные JVM в целевой системе
- jstat — отслеживает статистику JVM
- jstatd — отслеживает создание и завершение работы инструментальных JVM
4.
10. Инструменты для поиска и устранения неисправностей
Это экспериментальные инструменты, которые мы можем использовать для устранения неполадок. :
- информация — генерирует информацию о конфигурации для указанного процесса Java
- jmap — печатает карты памяти общих объектов или детали памяти кучи указанного процесса
- jsadebugd — подключается к процессу Java и действует как сервер отладки
- jstack — печатает трассировку стека Java потоков Java для данного процесса Java
5.Вывод
В этой статье мы определили, что основное различие между JVM, JRE и JDK заключается в их использовании.
Во-первых, мы описали, как JVM представляет собой абстрактную вычислительную машину, которая фактически выполняет байт-код Java.
Затем мы объяснили, как просто запускать приложения Java, используя JRE.
И, наконец, мы поняли, как разрабатывать Java-приложения, мы используем JDK.
Мы также потратили некоторое время на изучение инструментов и фундаментальных концепций этих компонентов.
Модель памяти
Java (JVM) — Управление памятью в Java
Понимание модели памяти JVM , Управление памятью Java очень важно, если вы хотите понять работу Java Garbage Collection . Сегодня мы рассмотрим управление памятью в Java, различные части памяти JVM и способы отслеживания и настройки сборки мусора.
Модель памяти Java (JVM)
Как вы можете видеть на изображении выше, память JVM разделена на отдельные части.В целом, память JVM Heap физически разделена на две части — молодого поколения и старого поколения .
Управление памятью в Java — Молодое поколение
Молодое поколение — это место, где создаются все новые объекты. Когда молодое поколение заполнено, выполняется сборка мусора. Эта сборка мусора называется Minor GC . Молодое поколение разделено на три части — Eden Memory и две Survivor Memory .
Важные сведения о пространствах молодого поколения:
- Большинство вновь созданных объектов находится в пространстве памяти Эдема.
- Когда пространство Эдема заполнено объектами, выполняется Малый сборщик мусора, и все выжившие объекты перемещаются в одно из оставшихся в живых.
- Minor GC также проверяет выжившие объекты и перемещает их в другое место выживших. Так что за один раз одно из ячеек выживших всегда пусто.
- Объекты, которые сохранились после многих циклов сборки мусора, перемещаются в область памяти старого поколения.Обычно это делается путем установления порога возраста объектов молодого поколения, прежде чем они смогут перейти в Старое поколение.
Управление памятью в Java — старое поколение
Память старого поколения содержит объекты, которые являются долгоживущими и уцелевшими после многих циклов Minor GC. Обычно сборка мусора выполняется в памяти старого поколения, когда она заполнена. Сборка мусора старого поколения называется Major GC и обычно занимает больше времени.
Stop the World Event
Все сборки мусора являются событиями «Stop the World», потому что все потоки приложений останавливаются до завершения операции.
Поскольку молодое поколение хранит недолговечные объекты, Minor GC работает очень быстро, и это не влияет на приложение.
Однако Major GC требует много времени, потому что он проверяет все живые объекты. Крупный сборщик мусора должен быть минимизирован, потому что это приведет к тому, что ваше приложение не будет отвечать на время сборки мусора.Поэтому, если у вас есть отзывчивое приложение и происходит много крупной сборки мусора, вы заметите ошибки тайм-аута.
Продолжительность работы сборщика мусора зависит от стратегии, используемой для сборки мусора. Вот почему необходимо отслеживать и настраивать сборщик мусора, чтобы избежать тайм-аутов в высокочувствительных приложениях.
Модель памяти Java — постоянное создание
Постоянное поколение или «постоянное поколение» содержит метаданные приложения, необходимые JVM для описания классов и методов, используемых в приложении.Обратите внимание, что Perm Gen не является частью памяти Java Heap.
Perm Gen заполняется JVM во время выполнения на основе классов, используемых приложением. Perm Gen также содержит классы и методы библиотеки Java SE. Объекты Perm Gen собираются мусором при полной сборке мусора.
Модель памяти Java — область методов
Область методов является частью пространства Perm Gen и используется для хранения структуры классов (констант времени выполнения и статических переменных) и кода для методов и конструкторов.
Модель памяти Java — пул памяти
Пулы памяти создаются менеджерами памяти JVM для создания пула неизменяемых объектов, если реализация поддерживает это.Пул строк — хороший пример такого типа пула памяти. Пул памяти может принадлежать Heap или Perm Gen, в зависимости от реализации диспетчера памяти JVM.
Модель памяти Java — Пул констант времени выполнения
Пул констант времени выполнения — это представление пула констант в классе во время выполнения для каждого класса. Он содержит константы времени выполнения и статические методы класса. Пул констант времени выполнения является частью области метода.
Модель памяти Java — память стека Java
Память стека Java используется для выполнения потока.Они содержат специфические для метода значения, которые недолговечны, и ссылки на другие объекты в куче, на которые ссылается метод. Вы должны прочитать разницу между стеком и памятью кучи.
Управление памятью в Java — переключатели памяти кучи Java
Java предоставляет множество переключателей памяти, которые мы можем использовать для установки размеров памяти и их соотношений. Некоторые из наиболее часто используемых переключателей памяти:
VM Switch | VM Switch Description |
---|---|
-Xms | Для установки начального размера кучи при запуске JVM |
-Xmx | Для установки максимального размер кучи. |
-Xmn | Для установки размера Молодого Поколения остальное пространство отводится Старому Поколению. |
-XX: PermGen | Для установки начального размера памяти постоянного поколения |
-XX: MaxPermGen | Для установки максимального размера Perm Gen |
-X16o Survivor соотношение пространства Eden и Survivor Space, например, если размер Young Generation составляет 10 м, а переключатель VM равен -XX: SurvivorRatio = 2, то 5 м будут зарезервированы для Eden Space и 2.По 5 м для обоих мест для выживших. Значение по умолчанию — 8. | |
-XX: NewRatio | Для обеспечения соотношения размеров старого / нового поколения. Значение по умолчанию — 2. |
В большинстве случаев указанных выше параметров достаточно, но если вы хотите проверить и другие параметры, посетите официальную страницу параметров JVM.
Управление памятью в Java — Сборка мусора Java
Сборка мусора Java — это процесс идентификации и удаления неиспользуемых объектов из памяти и освобождения пространства, выделяемого объектам, созданным для будущей обработки.Одной из лучших особенностей языка программирования Java является автоматическая сборка мусора , в отличие от других языков программирования, таких как C, где выделение и освобождение памяти выполняется вручную.
Сборщик мусора — это программа, работающая в фоновом режиме, которая просматривает все объекты в памяти и находит объекты, на которые не ссылается какая-либо часть программы. Все эти объекты, на которые нет ссылок, удаляются, а пространство освобождается для выделения другим объектам.
Один из основных способов сборки мусора состоит из трех шагов:
- Маркировка : Это первый шаг, на котором сборщик мусора определяет, какие объекты используются, а какие не используются.
- Обычное удаление : Сборщик мусора удаляет неиспользуемые объекты и освобождает свободное пространство для выделения другим объектам.
- Удаление с уплотнением : для повышения производительности после удаления неиспользуемых объектов все уцелевшие объекты можно переместить вместе.Это повысит производительность выделения памяти более новым объектам.
Есть две проблемы с простым подходом пометки и удаления.
- Во-первых, это неэффективно, потому что большинство вновь созданных объектов перестанет использоваться.
- Во-вторых, объекты, которые используются для нескольких циклов сборки мусора, скорее всего, будут использоваться и в будущих циклах.
Вышеупомянутые недостатки простого подхода являются причиной того, что Java Garbage Collection является поколением , и у нас есть мест для молодого поколения и для старого поколения в памяти кучи.Выше я уже объяснял, как объекты сканируются и перемещаются из одного поколения в другое на основе Minor GC и Major GC.
Управление памятью в Java — типы сборки мусора Java
Существует пять типов типов сборки мусора, которые мы можем использовать в наших приложениях. Нам просто нужно использовать переключатель JVM, чтобы включить стратегию сборки мусора для приложения. Давайте рассмотрим каждую из них по очереди.
- Последовательный GC (-XX: + UseSerialGC) : Последовательный GC использует простой подход mark-sweep-compact для сборки мусора молодого и старого поколений i.e Minor и Major GC.Serial GC полезен на клиентских машинах, таких как наши простые автономные приложения и машины с меньшим ЦП. Это хорошо для небольших приложений с небольшим объемом памяти.
- Параллельный сборщик мусора (-XX: + UseParallelGC) : Параллельный сборщик мусора такой же, как последовательный сборщик мусора, за исключением того, что порождает N потоков для сборки мусора молодого поколения, где N — количество ядер ЦП в системе. Мы можем контролировать количество потоков с помощью параметра JVM
-XX: ParallelGCThreads = n
.Параллельный сборщик мусора также называется сборщиком пропускной способности, поскольку он использует несколько процессоров для ускорения работы сборщика мусора. Параллельный сборщик мусора использует один поток для сборки мусора старого поколения. - Параллельный старый сборщик мусора (-XX: + UseParallelOldGC) : то же самое, что и параллельный сборщик мусора, за исключением того, что он использует несколько потоков для сборки мусора как молодого, так и старого поколения.
- Коллектор параллельного анализа меток (CMS) (-XX: + UseConcMarkSweepGC) : Сборщик CMS также называется параллельным сборщиком низкой паузы.Он занимается сборкой мусора для Старого поколения. Сборщик CMS пытается минимизировать паузы из-за сборки мусора, выполняя большую часть работы по сборке мусора одновременно с потоками приложения. Сборщик CMS в молодом поколении использует тот же алгоритм, что и параллельный сборщик. Этот сборщик мусора подходит для отзывчивых приложений, где мы не можем позволить себе более длительные паузы. Мы можем ограничить количество потоков в сборщике CMS, используя параметр JVM
-XX: ParallelCMSThreads = n
. - Сборщик мусора G1 (-XX: + UseG1GC) : Сборщик мусора Garbage First или G1 доступен в Java 7, и его долгосрочная цель — заменить сборщик CMS. Сборщик G1 — это параллельный, параллельный и постепенно уплотняющийся сборщик мусора с малой паузой. Первый сборщик мусора не работает, как другие сборщики, и нет концепции пространства молодого и старого поколений. Он делит пространство кучи на несколько областей кучи одинакового размера. Когда вызывается сборщик мусора, он сначала собирает область с меньшим объемом оперативных данных, следовательно, «сначала мусор».Вы можете найти более подробную информацию об этом в документации Oracle по сборщику мусора.
Управление памятью в Java — мониторинг сборки мусора Java
Мы можем использовать командную строку Java, а также инструменты пользовательского интерфейса для мониторинга действий приложения по сбору мусора. В моем примере я использую одно из демонстрационных приложений, предоставляемых загрузками Java SE.
Если вы хотите использовать одно и то же приложение, перейдите на страницу загрузок Java SE и загрузите JDK 7 и JavaFX Demos and Samples .Я использую пример приложения Java2Demo.jar , и он находится в каталоге jdk1.7.0_55 / demo / jfc / Java2D
. Однако это необязательный шаг, и вы можете запускать команды мониторинга GC для любого Java-приложения.
Команда, использованная мной для запуска демонстрационного приложения:
pankaj @ Pankaj: ~ / Downloads / jdk1.7.0_55 / demo / jfc / Java2D $ java -Xmx120m -Xms30m -Xmn10m -XX: PermSize = 20m -XX : MaxPermSize = 20 м -XX: + UseSerialGC -jar Java2Demo.jar
jstat
Мы можем использовать инструмент командной строки jstat
для мониторинга памяти JVM и действий по сборке мусора.Он поставляется со стандартным JDK, поэтому вам не нужно ничего делать, чтобы его получить.
Для выполнения jstat
вам необходимо знать идентификатор процесса приложения, вы можете легко получить его с помощью ps -eaf | команда grep java
.
pankaj @ Pankaj: ~ $ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 21:48 ttys000 0: 21.66 / usr / bin / java -Xmx120m -Xms30m -Xmn10m -XX: PermSize = 20m -XX: MaxPermSize = 20m -XX: + UseG1GC -jar Java2Demo.jar
501 14073 14045 0 21:48 ttys002 0:00.00 grep Java2Demo.jar
Итак, идентификатор процесса для моего java-приложения — 9582. Теперь мы можем запустить команду jstat , как показано ниже.
pankaj @ Pankaj: ~ $ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024,0 1024,0 0,0 0,0 8192,0 7933,3 42108,0 23401,3 20480,0 19990,9 157 0,274 40 1,381 1,654
1024,0 1024,0 0,0 0,0 8192,0 8026,5 42108.0 23401,3 20480,0 19990,9 157 0,274 40 1,381 1,654
1024,0 1024,0 0,0 0,0 8192,0 8030,0 42108,0 23401,3 20480,0 19990,9 157 0,274 40 1,381 1,654
1024,0 1024,0 0,0 0,0 8192,0 8122,2 42108,0 23401,3 20480,0 19990,9 157 0,274 40 1,381 1,654
1024,0 1024,0 0,0 0,0 8192,0 8171,2 42108,0 23401,3 20480,0 19990,9 157 0,274 40 1,381 1,654
1024,0 1024,0 48,7 0,0 8192,0 106,7 42108,0 23401.3 20480,0 19990,9 158 0,275 40 1,381 1,656
1024,0 1024,0 48,7 0,0 8192,0 145,8 42108,0 23401,3 20480,0 19990,9 158 0,275 40 1,381 1,656
Последний аргумент для jstat — это временной интервал между каждым выводом, поэтому он будет печатать данные о памяти и сборке мусора каждую секунду.
Давайте рассмотрим каждый столбец один за другим.
- S0C и S1C : в этом столбце отображается текущий размер областей Survivor0 и Survivor1 в КБ.
- S0U и S1U : в этом столбце показано текущее использование областей Survivor0 и Survivor1 в КБ. Обратите внимание, что одна из оставшихся в живых областей все время пуста.
- EC и EU : эти столбцы показывают текущий размер и использование пространства Eden в КБ. Обратите внимание, что размер EU увеличивается, и как только он пересекает EC, вызывается Minor GC, а размер EU уменьшается.
- OC и OU : эти столбцы показывают текущий размер и текущее использование старого поколения в КБ.
- PC и PU : в этих столбцах отображается текущий размер и текущее использование Perm Gen в КБ.
- YGC и YGCT : столбец YGC отображает количество событий GC, произошедших в молодом поколении. Столбец YGCT отображает суммарное время операций ГХ для молодого поколения. Обратите внимание, что оба они увеличиваются в той же строке, где значение EU опускается из-за незначительного GC.
- FGC и FGCT : столбец FGC отображает количество произошедших событий Full GC.Столбец FGCT отображает суммарное время для операций полного ГХ. Обратите внимание, что время полной сборки мусора слишком велико по сравнению с временем сборки мусора молодого поколения.
- GCT : в этом столбце отображается общее суммарное время для операций ГХ. Обратите внимание, что это сумма значений столбцов YGCT и FGCT.
Преимущество jstat в том, что его можно запускать и на удаленных серверах, где у нас нет графического интерфейса. Обратите внимание, что сумма S0C, S1C и EC составляет 10 м, как указано в опции JVM -Xmn10m
.
Java VisualVM с Visual GC
Если вы хотите видеть операции с памятью и сборщиком мусора в графическом интерфейсе пользователя, вы можете использовать инструмент jvisualvm
. Java VisualVM также является частью JDK, поэтому вам не нужно загружать его отдельно.
Просто запустите команду jvisualvm
в терминале, чтобы запустить приложение Java VisualVM. После запуска вам необходимо установить плагин Visual GC из меню Инструменты — <Плагины, как показано на изображении ниже.
После установки Visual GC просто откройте приложение в левом столбце и перейдите в раздел Visual GC .Вы получите изображение памяти JVM и детали сборки мусора, как показано на изображении ниже.
Настройка сборки мусора Java
Настройка сборки мусора Java должна быть последней опцией, которую следует использовать для увеличения пропускной способности вашего приложения, и только тогда, когда вы видите падение производительности из-за увеличения времени сборки мусора, вызывающего таймаут приложения.
Если вы видите ошибки java.lang.OutOfMemoryError: PermGen space
в журналах, попробуйте отслеживать и увеличивать объем памяти Perm Gen с помощью параметров -XX: PermGen и -XX: MaxPermGen JVM.Вы также можете попробовать использовать -XX: + CMSClassUnloadingEnabled
и проверить его работу с помощью сборщика мусора CMS.
Если вы видите много операций с полным сборщиком мусора, попробуйте увеличить объем памяти старого поколения.
Общая настройка сборки мусора требует много усилий и времени, и для этого нет жесткого правила. Вам нужно будет попробовать разные варианты и сравнить их, чтобы выбрать лучший, подходящий для вашего приложения.
Это все, что касается модели памяти Java, управления памятью в Java и сборки мусора. Надеюсь, это поможет вам понять память JVM и процесс сборки мусора.
10 баллов о Java Heap Space или Java Heap Memory
10 баллов о памяти кучи Java
Когда я начинал программировать на Java, я не знал, что такое java heap или пространство кучи в Java, я даже не знал, где создается объект в Java, когда я начал заниматься профессиональным программированием, я столкнулся с ошибкой java.lang.OutOfMemoryError в Tomcat я понял, что такое куча в Java или Java Heap Space. Такое случается с большинством программистов, потому что выучить язык легко, но изучить основы сложно, поскольку не существует формального процесса, который научил бы вас всем основам программирования, его опыту и работе, раскрывающей секрет программирования.
Для Java-разработчиков знание кучи в Java, установка размера пространства кучи Java, работа с пространством кучи Java OutOfMemoryError, анализ дампов кучи очень важны. Вы должны прочитать «Производительность Java» от Чарли Ханта, чтобы узнать больше о том, как устранять проблемы с производительностью Java, профилированием и расширенными инструментами для получения дампа кучи и анализа его для формирования проблемных областей.
Это руководство по Java Heap предназначено для моих братьев-новичков, которые плохо знакомы с программированием и изучают его. Слишком большая разница, если вы знаете основы и лежащие в основе, пока вы не узнаете, что объект создан в куче, вы не сможете понять, почему OutOfMemoryError возникает в куче.
Я пытаюсь предоставить как можно больше информации о куче в Java, но хотел бы, чтобы вы, ребята, внесли свой вклад и поделились своими знаниями о куче в Java на благо всех.
Кстати, если вы запутались между кучей и стеком, где создаются ваши локальные переменные, вы также можете проверить разницу между кучей и стековой памятью в Java.
И, если вы серьезно настроены улучшить свои продвинутые навыки работы с JVM и изучаете такие вещи, как сбор и анализ дампов кучи, то настоятельно рекомендую вам присоединиться к курсу Java Application Performance and Memory Management на Udemy.
Это один из продвинутых курсов для программистов на Java, чтобы узнать больше об управлении производительностью и памятью, включая устранение утечек памяти в Java.
Что такое пространство кучи в Java?
При запуске программы Java виртуальная машина Java получает часть памяти от операционной системы. Виртуальная машина Java или JVM использует эту память для всех своих нужд, и часть этой памяти является памятью кучи вызовов java.
Куча в Java обычно располагается внизу адресного пространства и перемещается вверх.всякий раз, когда мы создаем объект с помощью оператора new или любым другим способом, объекту выделяется память из кучи, и когда объект умирает или сборщик мусора, память возвращается в пространство кучи в Java, чтобы узнать больше о сборке мусора, посмотрите, как сборка мусора работает в Ява.
Как увеличить размер кучи в Java
Размер кучи по умолчанию в Java составляет 128 МБ на большинстве 32-битных JVM Sun, но он сильно варьируется от JVM к JVM, например. по умолчанию максимальный и начальный размер кучи для 32-разрядной операционной системы Solaris (SPARC Platform Edition) составляет -Xms = 3670K и -Xmx = 64M, а значения параметров размера кучи по умолчанию в 64-разрядных системах были увеличены примерно на 30%.
Кроме того, если вы используете сборщик мусора с пропускной способностью в Java 1.5, максимальный размер кучи JVM по умолчанию будет Физическая память / 4, а начальный размер кучи по умолчанию будет Физическая память / 16. Другой способ найти размер кучи JVM по умолчанию — запустить приложение с параметрами кучи по умолчанию и отслеживать с помощью JConsole, которая доступна в JDK 1.5 и более поздних версиях, на вкладке VMSummary вы сможете увидеть максимальный размер кучи.
Кстати, вы можете увеличить размер кучи java в зависимости от потребностей вашего приложения, и я всегда рекомендую это, чтобы избежать использования значений кучи JVM по умолчанию.если ваше приложение велико и создано много объектов, вы можете изменить размер кучи с помощью параметров JVM -Xms и -Xmx. Xms обозначает начальный размер кучи, а -Xmx обозначает максимальный размер кучи в Java.
Есть еще один параметр, называемый -Xmn, который обозначает размер нового поколения Java Heap Space. Единственное, что вы не можете изменять размер кучи в Java динамически, вы можете указать только параметр Java Heap Size при запуске JVM. Я поделился некоторыми более полезными параметрами JVM, связанными с пространством кучи Java и сборкой мусора, в моем сообщении. 10 параметров JVM, которые должен знать программист Java, вы можете найти их полезными.
Обновление:
Что касается размера кучи по умолчанию в Java, из обновления Java 6 18 произошли значительные изменения в том, как JVM вычисляет размер кучи по умолчанию на 32- и 64-разрядных машинах и в режиме JVM клиента и сервера:
1) Начальное пространство кучи и максимальное пространство кучи больше для повышения производительности.
2) Максимальное пространство кучи по умолчанию составляет 1/2 физической памяти размером до 192 байт и 1/4 физической памяти для размера до 1 ГБ. Таким образом, для машины с объемом памяти 1 ГБ максимальный размер кучи составляет 256 МБ 2.максимальный размер кучи не будет использоваться до тех пор, пока программа не создаст достаточно объекта для заполнения начального пространства кучи, которое будет намного меньше, но не менее 8 МБ или 1/64 части физической памяти размером до 1 ГБ.
3) для виртуальной машины Java Server по умолчанию максимальный размер кучи составляет 1 ГБ для 4 ГБ физической памяти на 32-разрядной JVM. для 64-битной JVM это 32 ГБ для физической памяти 128 ГБ. Чтобы узнать больше о том, сколько памяти вы можете установить в 32-битной и 64-битной JVM в различных операционных системах, например Windows 8, Linux или Solaris см. Здесь.
Куча Java и сборка мусора
Как мы знаем, объекты создаются внутри кучи, а сборка мусора — это процесс, который удаляет мертвые объекты из пространства кучи Java и возвращает память обратно в кучу в Java. Ради сборки мусора куча разделена на три основных региона: новое поколение, старое или постоянное поколение и пермское пространство.
Новое поколение кучи Java — это часть памяти кучи Java, где хранится вновь созданный объект. В ходе работы приложения многие объекты были созданы и умерли, но они остались живыми, и они были перемещены в старое или постоянное поколение потоком сборщика мусора Java на Major или полная сборка мусора.Пермское пространство Java Heap — это место, где JVM хранит метаданные о классах и методах, пуле строк и деталях уровня класса.
Вы можете увидеть мою статью Как работает сборка мусора в Java для получения дополнительной информации о куче в Java и сборке мусора.
OutOfMemoryError в куче Java
Когда JVM запускается, пространство кучи JVM равно начальному размеру кучи, указанному параметром -Xms, по мере выполнения приложения создается больше объектов, а пространство кучи расширяется для размещения новых объектов.JVM также периодически запускает сборщик мусора, чтобы освободить память от мертвых объектов.
JVM расширяет кучу в Java где-то рядом с максимальным размером кучи, заданным параметром -Xmx, и если для создания новых объектов в куче java больше не осталось памяти, JVM выдает ошибку java.lang.OutOfMemoryError и ваше приложение умирает.
Перед тем, как выбросить OutOfMemoryError Нет места в Java Heap, JVM пытается запустить сборщик мусора, чтобы освободить любое доступное пространство, но даже после этого не так много свободного места в Heap в Java приводит к OutOfMemoryError.
Чтобы устранить эту ошибку, вам необходимо понять профиль объекта вашего приложения, т.е. какой объект вы создаете, какие объекты занимают, сколько памяти и т. Д., Вы можете использовать профилировщик или анализатор кучи для устранения ошибки OutOfMemoryError в Java.
Сообщение об ошибке «java.lang.OutOfMemoryError: пространство кучи Java» означает, что куча Java не имеет достаточного пространства и не может быть расширена дальше, в то время как сообщение об ошибке «java.lang.OutOfMemoryError: пространство PermGen space» появляется, когда постоянное создание кучи Java full, приложение не сможет загрузить класс или выделить интернированную строку.
И, если вы серьезно настроены улучшить свои продвинутые навыки работы с JVM и изучаете такие вещи, как сбор и анализ дампов кучи, то настоятельно рекомендую вам присоединиться к курсу Java Application Performance and Memory Management на Udemy.
Как увеличить пространство кучи Java на Maven и ANT
Нам часто нужно увеличивать размер кучи Maven или ANT, потому что, как только количество классов увеличивается, инструмент сборки требует больше памяти для обработки и сборки и часто вызывает OutOfMemoryError, которого мы можем избежать, изменив или увеличив память кучи JVM.Подробнее см. Мой пост Как увеличить память кучи Java для Ant или Maven
Дамп кучи Java
Дамп кучи Java — это снимок памяти кучи Java в определенный момент времени. Это очень полезно для анализа или устранения любой утечки памяти в Java или любой ошибки java.lang.OutOfMemoryError. В JDK есть инструменты, которые помогут вам получить дамп кучи, и есть инструмент для анализа кучи, который поможет вам анализировать дамп кучи java.
Вы также можете использовать команду «jmap» для получения дампа кучи java, это создаст файл дампа кучи, а затем вы можете использовать «jhat — Java Heap Analysis Tool» для анализа этих дампов кучи.Вам также следует прочитать «Полное руководство по производительности Java» Скотта Оукса, чтобы узнать больше о настройке производительности и профилировании Java. Это одна из книг по производительности Java, которую должен прочитать любой старший Java-разработчик.
10 баллов о Java Heap Space
1. Память Java Heap — это часть памяти, выделенной JVM операционной системой.
2. Каждый раз, когда мы создаем объекты, они создаются внутри Heap в Java.
3. Пространство кучи Java разделено на три области или поколения для сбора мусора, которые называются «Новое поколение», «Старое или существующее поколение» или «Пермское пространство».Постоянная генерация — это сборщик мусора во время полного gc в JVM горячей точки.
4. Вы можете увеличить или изменить размер пространства кучи Java с помощью параметров командной строки JVM -Xms, -Xmx и -Xmn. не забудьте добавить слово «M» или «G» после указания размера, чтобы указать Mega или Gig. например, вы можете установить размер кучи java равным 258 МБ, выполнив следующую команду java -Xmx256m HelloWorld.
5. Вы можете использовать JConsole или Runtime.maxMemory (), Runtime.totalMemory (), Runtime.freeMemory () для запроса о программном размере кучи в Java.См. Мой пост Как найти использование памяти в программе Java для более подробной информации.
6. Вы можете использовать команду «jmap» для получения дампа кучи в Java и «jhat» для анализа этого дампа кучи.
7. Пространство кучи Java отличается от стека, который используется для хранения иерархии вызовов и локальных переменных.
8. Сборщик мусора Java отвечает за освобождение памяти от мертвых объектов и возврат в пространство кучи Java.
9. Не паникуйте, когда вы получаете java.lang.OutOfMemoryError, иногда просто вопрос увеличения размера кучи, но если это повторяется, то ищите утечку памяти в Java.
10. Используйте средство профилирования и анализатора дампа кучи, чтобы понять пространство кучи Java и сколько памяти выделено для каждого объекта.
Устранение проблем с памятью в приложениях Java
Основные выводы
- Устранение проблем с памятью может быть сложной задачей, но правильный подход и правильный набор инструментов могут существенно упростить процесс.
- Java HotSpot JVM может сообщать о нескольких типах сообщений OutOfMemoryError, и важно четко понимать эти сообщения об ошибках и иметь широкий набор инструментов диагностики и устранения неполадок в нашем наборе инструментов для диагностики и устранения этих проблем.
- В этой статье мы рассмотрим широкий спектр диагностических инструментов, которые могут быть очень полезны при устранении проблем с памятью, в том числе:
- Параметры JVM HeapDumpOnOutOfMemoryError и PrintClassHistogram
- Eclipse MAT
- Java VisualVM
- JConsole
- джат
- YourKit
- jmap
- jcmd
- Java Flight Recorder и Java Mission Control
- Журналы ГХ
- не более
- Средства обнаружения утечек встроенной памяти, такие как dbx, libumem, valgrind, purify и т. Д.
Для процесса Java существует несколько пулов или пространств памяти — куча Java, Metaspace, PermGen (в версиях до Java 8) и собственная куча.
Каждый из этих пулов памяти может столкнуться со своим собственным набором проблем с памятью, например аномальным ростом памяти, замедлением работы приложения или утечками памяти, все из которых в конечном итоге может проявиться в форме OutOfMemoryError для этих пространств.
В этой статье мы попытаемся понять, что означают эти сообщения об ошибках OutOfMemoryError, какие диагностические данные мы должны собирать для диагностики и устранения этих проблем, а также исследуем некоторые инструменты для сбора этих данных и их анализа для решения этих проблем с памятью.В этой статье основное внимание уделяется способам решения и предотвращения этих проблем с памятью в производственных средах.
Сообщение OutOfMemoryError, сообщаемое виртуальной машиной Java HotSpot, дает четкое указание на то, какой объем памяти истощается. Давайте подробно рассмотрим эти различные сообщения OutOfMemoryError, разберемся с ними и исследуем их вероятные причины, а также способы их устранения и устранения.
OutOfMemoryError: пространство кучи Java
Исключение в потоке "main" java.lang.OutOfMemoryError: пространство кучи Java
в java.util.Arrays.copyOfRange (неизвестный источник)
в java.lang.String. (Неизвестный источник)
в java.io.BufferedReader.readLine (Неизвестный источник)
в java.io.BufferedReader.readLine (Неизвестный источник)
в com.abc.ABCParser.dump (ABCParser.java:23)
в com.abc.ABCParser.mainABCParser.java:59)
Это сообщение означает, что у JVM не осталось свободного места в куче Java, и она не может продолжить выполнение программы.Наиболее частой причиной таких ошибок является то, что указанный максимальный размер кучи Java недостаточен для размещения полного набора живых объектов. Один простой способ проверить, достаточно ли велика куча Java, чтобы содержать все живые объекты в JVM, — это проверить журналы GC.
688995.775: [Полный GC [PSYoungGen: 46400K-> 0K (471552K)] [ParOldGen: 1002121K-> 304673K (1036288K)] 1048
521K-> 304673K (1507840K) [PSPermGen: 253230K-> 253230K (1048576K)], 0,3402350 секунд] [Times: user = 1.48
sys = 0,00, real = 0,34 секунды]
Из приведенной выше записи в журнале видно, что после Full GC
занятость кучи снижается с 1 ГБ (1048521 КБ) до 305 МБ (304673 КБ), что означает, что 1,5 ГБ (1507840 КБ), выделенные для кучи, достаточно велики, чтобы содержать набор данных в реальном времени.
Теперь давайте посмотрим на следующую активность GC:
20,343: [Полный GC (эргономика) [PSYoungGen: 12799K-> 12799K (14848K)] [ParOldGen: 33905K-> 33905K (34304K)] 46705K-> 46705K (49152K), [Metaspace: 2921K-> 2921K] (1056768K) (1056768K) , 0.4595734 секунды] [Время: пользователь = 1,17, системное = 0,00, реальное = 0,46 секунды]
...... несколько полных сборщиков мусора ......
22.640: [Полный GC (эргономика) [PSYoungGen: 12799K-> 12799K (14848K)] [ParOldGen: 33911K-> 33911K (34304K)] 46711K-> 46711K (49152K), [Metaspace: 2921K-> 2921K (1056768K) 0,4648764 секунды] [Время: пользователь = 1,11, системный = 0,00, реальный = 0,46 секунды]
23.108: [Полный GC (эргономика) [PSYoungGen: 12799K-> 12799K (14848K)] [ParOldGen: 33913K-> 33913K (34304K)] 46713K-> 46713K (49152K), [Metaspace: 2921K-> 2921K (1056768K)] , 0.4380009 секунд] [Время: пользователь = 1,05, системное = 0,00, реальное = 0,44 секунды]
23,550: [Полный GC (эргономика) [PSYoungGen: 12799K-> 12799K (14848K)] [ParOldGen: 33914K-> 33914K (34304K)] 46714K-> 46714K (49152K), [Metaspace: 2921K-> 2921K (1056768K)] , 0,4767477 секунд] [Время: пользователь = 1,15, системное = 0,00, реальное = 0,48 секунды]
24.029: [Полный GC (эргономика) [PSYoungGen: 12799K-> 12799K (14848K)] [ParOldGen: 33915K-> 33915K (34304K)] 46715K-> 46715K (49152K), [Metaspace: 2921K-> 2921K (1056768K)] , 0,41 секунд] [Times: user = 1.12 sys = 0.00, real = 0.42 secs] Исключение в потоке «main» java.lang.OutOfMemoryError: превышен предел накладных расходов GC на oom.main (oom.java:15)
Судя по частоте сообщений «Полный сборщик мусора» в дампе, мы видим, что несколько подряд идущих полных сборщиков мусора пытаются освободить пространство в куче Java, но куча полностью заполнена, и сборщики мусора не могут чтобы освободить место. Эти частые полные сборщики мусора негативно влияют на производительность приложения, замедляя его сканирование. В этом примере предполагается, что требования приложения к куче превышают указанный размер кучи Java.Увеличение размера кучи поможет избежать этих полных GC и обойти OutOfMemoryError. Размер кучи Java можно увеличить с помощью параметра -Xmx JVM:
java –Xmx1024m –Xms1024m Тест
OutOfMemoryError также может указывать на утечку памяти в приложении. Утечки памяти часто очень трудно обнаружить, особенно медленные утечки памяти. Как мы знаем, утечка памяти происходит, когда приложение непреднамеренно удерживает ссылки на объекты в куче, предотвращая их сборку мусора.Эти непреднамеренно удерживаемые объекты могут со временем расти в куче, в конечном итоге заполняя все пространство кучи Java, вызывая частые сборки мусора и, в конечном итоге, завершение программы с OutOfMemoryError.
Обратите внимание, что всегда рекомендуется включать ведение журнала GC, даже в производственных средах, чтобы облегчить обнаружение и устранение проблем с памятью по мере их возникновения. Для включения регистрации ГХ можно использовать следующие параметры:
-XX: + PrintGCDetails
-XX: + PrintGCTimeStamps
-XX: + PrintGCDateStamps
-Xloggc: <файл журнала gc>
Первым шагом в обнаружении утечек памяти является отслеживание активности приложения.Live-set — это количество кучи Java, используемое после полного GC. Если live-set увеличивается с течением времени даже после того, как приложение достигло стабильного состояния и находится под стабильной нагрузкой, это может указывать на утечку памяти. Использование кучи можно отслеживать с помощью инструментов, включая Java VisualVM, Java Mission Control и JConsole, а также извлекать из журналов GC.
Куча Java: сбор диагностических данных
В этом разделе мы рассмотрим, какие диагностические данные следует собирать для устранения ошибок OutOfMemoryErrors в куче Java, а также инструменты, которые могут помочь нам собрать необходимые диагностические данные.
Отвалы кучи
Дампы кучи — это самые важные данные, которые мы можем собрать при устранении утечек памяти. Дампы кучи можно собирать с помощью jcmd, jmap, JConsole и параметра JVM HeapDumpOnOutOfMemoryError, как показано ниже.
-
jcmd <идентификатор процесса / основной класс> GC.heap_dump filename = heapdump.dmp
-
jmap -dump: format = b, file = snapshot.jmap pid
-
Утилита JConsole с использованием Mbean HotSpotDiagnostic
-
-XX: + HeapDumpOnOutOfMemoryError
java -XX: + PrintGCDetails -XX: + PrintGCTimeStamps -Xmx20m -XX: + HeapDumpOnOutOfMemoryError oom
0.402: [GC (сбой выделения) [PSYoungGen: 5564K-> 489K (6144K)] 5564K-> 3944K (19968K), 0,0196154 секунды] [Times: user = 0,05 sys = 0,00, real = 0,02 секунды]
0,435: [GC (сбой выделения) [PSYoungGen: 6000K-> 496K (6144K)] 9456K-> 8729K (19968K), 0,0257773 секунды] [Times: user = 0,05 sys = 0,00, real = 0,03 секунды]
0,469: [GC (сбой выделения) [PSYoungGen: 5760K-> 512K (6144K)] 13994K-> 13965K (19968K), 0,0282133 секунды] [Times: user = 0,05 sys = 0,00, real = 0,03 секунды]
0,499: [Полная сборка мусора (эргономика) [PSYoungGen: 512K-> 0K (6144K)] [ParOldGen: 13453K-> 12173K (13824K)] 13965K-
> 12173K (19968K), [метапространство: 2922K-> 2922K (1056768K)], 0.6941054 с] [Время: пользователь = 1,45, системное = 0,00, реальное = 0,69 с] 1,205: [Полная сборка мусора (эргономика) [PSYoungGen: 5632K-> 2559K (6144K)] [ParOldGen: 12173K-> 13369K (13824K)] 17805K-
> 15929K (19968K), [Metaspace: 2922K-> 2922K (1056768K)], 0,3933345 секунд] [Times: user = 0,69 sys = 0,00, real = 0,39 секунды]
1.606: [Полная GC (эргономика) [PSYoungGen: 4773K-> 4743K (6144K)] [ParOldGen: 13369K-> 13369K (13824K)] 18143K-
> 18113K (19968K), [Metaspace: 2922K-> 2922K (1056768K)], 0,3009828 секунд] [Times: user = 0.72 sys = 0,00, real = 0,30 секунды]
1.911: [Полный GC (сбой выделения) [PSYoungGen: 4743K-> 4743K (6144K)] [ParOldGen: 13369K-> 13357K (13824K)] 18113K-
> 18101K (19968K), [Metaspace: 2922K-> 2922K (1056768K)], 0,6486744 секунды] [Times: user = 1,43 sys = 0,00, real = 0,65 секунды]
java.lang.OutOfMemoryError: пространство кучи Java
Выгрузка кучи в java_pid26504.hprof ...
Создан файл дампа кучи [30451751 байт за 0,510 секунды] Исключение в потоке "main" java.lang.OutOfMemoryError: Пространство кучи Java
в java.util.Arrays.copyOf (Arrays.java:3210)
в java.util.Arrays.copyOf (Arrays.java:3181)
в java.util.ArrayList.grow (ArrayList.java:261)
в java.util.ArrayList.ensureExplicitCapacity (ArrayList.java:235)
в java.util.ArrayList.ensureCapacityInternal (ArrayList.java:227)
в java.util.ArrayList.add (ArrayList.java:458)
в oom.main (oom.java:14)
Обратите внимание, что параллельный сборщик мусора может постоянно пытаться освободить место в куче, вызывая частые последовательные полные сборщики мусора, даже если выгода от этих усилий невелика, а куча почти заполнена.Это влияет на производительность приложения и может задержать возврат системы в исходное состояние. Этой ситуации можно избежать, настроив значения для -XX: GCTimeLimit
и -XX: GCHeapFreeLimit
.
GCTimeLimit
устанавливает верхний предел времени, которое GC могут проводить в процентах от общего времени. Его значение по умолчанию 98%. Уменьшение этого значения сокращает допустимое количество времени, которое может быть потрачено на сборку мусора. GCHeapFreeLimit
устанавливает нижний предел объема пространства, который должен быть свободен после сборки мусора, представленный как процент от максимальной кучи.Значение по умолчанию — 2%. Увеличение этого значения означает, что сборщики мусора должны освободить больше места в куче. OutOfMemoryError
выдается после полного GC, если предыдущие 5 последовательных GC (могут быть второстепенными или полными) не смогли удержать стоимость GC ниже GCTimeLimit и не смогли освободить пространство GCHeapFreeLimit
.
Например, установка GCHeapFreeLimit на 8 процентов может помочь сборщику мусора не застрять в цикле последовательного вызова полных сборщиков мусора, когда он не может освободить по крайней мере 8% кучи и превышает GCTimeLimit 5 раз подряд. GC.
Гистограммы кучи
Иногда нам нужно быстро взглянуть на то, что растет в нашей куче, минуя длинный путь сбора и анализа дампов кучи с помощью инструментов анализа памяти. Гистограммы кучи могут дать нам быстрое представление об объектах, присутствующих в нашей куче, и сравнение этих гистограмм может помочь нам найти лучших производителей в нашей куче Java.
-
-XX: + PrintClassHistogram и Control + Break
-
jcmd <идентификатор процесса / основной класс> GC.class_histogram filename = Myheaphistogram
-
jmap -histo pid
-
jmap -histo
core_file
Пример выходных данных ниже показывает, что экземпляры String, Double, Integer и Object []
занимают больше всего места в куче Java и со временем их число растет, что указывает на то, что они потенциально могут вызывать утечку памяти:
Java Записи полетов
Flight Recordings с включенной статистикой кучи могут быть действительно полезны при устранении утечки памяти, показывая нам объекты кучи и основные источники в куче с течением времени.Чтобы включить статистику кучи, вы можете использовать Java Mission Control и включить «Статистику кучи», перейдя в «Окно-> Диспетчер шаблонов записи полета», как показано ниже.
Или отредактируйте вручную файлы .jfc, установив для параметра heap-statistics-enabled значение true.
true
everyChunk
Записи полета могут быть созданы любым из следующих способов:
- Опции бортового регистратора JVM, e.грамм.
-XX: + UnlockCommercialFeatures -XX: + FlightRecorder
-XX: StartFlightRecording = задержка = 20 с, продолжительность = 60 с, имя = MyRecording,
имя_файла = C: \ TEMP \ myrecording.jfr, настройки = профиль
- Диагностическая команда Java: jcmd
jcmd 7060 JFR.start name = Настройки MyRecording = задержка профиля = 20 секунд продолжительность = 2 м имя файла = c: \ TEMP \ myrecording.jfr
«Записи полета» могут помочь нам определить тип протекающих объектов, но чтобы выяснить, что вызывает утечку этих объектов, нам нужны дампы кучи.
Java Heap: анализ диагностических данных
Анализ дампа кучи
Дампов кучи можно проанализировать с помощью следующих инструментов:
- Eclipse MAT — (Memory Analyzer Tool) — инструмент, разработанный сообществом для анализа дампов кучи. Вот некоторые из замечательных функций, которые он предлагает:
- Подозреваемые на утечку: он может проверять дамп кучи на предмет подозреваемых в утечке, сообщающих об объектах, которые подозрительно сохраняют большой объем кучи
- Гистограммы: перечисляет количество объектов в классе, а также мелкую и сохраняемую кучу, удерживаемую этими объектами.Объекты на гистограмме можно легко отсортировать или отфильтровать с помощью регулярных выражений. Это помогает увеличить масштаб и сосредоточиться на объектах, которые, как мы подозреваем, могут протекать. Он также имеет возможность сравнивать гистограммы из двух дампов кучи и может показывать разницу в количестве экземпляров для каждого класса. Это помогает найти лучших производителей в куче Java, которые можно дополнительно изучить, чтобы определить корни, удерживающие эти объекты в куче.
- Недостижимые объекты: удивительная способность MAT состоит в том, что он позволяет включать или исключать недостижимые / мертвые объекты в своем рабочем наборе объектов.Если мы не хотим смотреть на объекты, которые недоступны и могут быть собраны в следующем цикле сборки мусора, и нас интересуют только достижимые объекты, тогда эта функция очень удобна.
- Duplicate Classes: показывает повторяющиеся классы, загруженные несколькими загрузчиками классов.
- Путь к корням сборщика мусора: может отображать цепочки ссылок на корни сборщика мусора (объекты, поддерживаемые самой JVM), отвечающие за сохранение объектов в куче.
- OQL: мы можем использовать язык объектных запросов для исследования объектов в дампах кучи.Его расширенный OQL облегчает написание сложных запросов, которые помогают глубже копаться в дампах кучи.
- Java VisualVM — универсальный инструмент для мониторинга, профилирования и устранения неполадок приложений Java. Он доступен как инструмент JDK, а также может быть загружен с GitHub. Одна из его функций — анализ дампа кучи. Он может создавать дампы кучи отслеживаемого приложения, а также загружать и анализировать их. Из дампов кучи он может отображать гистограммы классов, экземпляры класса, а также может помочь найти корни сборки мусора конкретных экземпляров.
- jhat (в нашей папке
/ bin.) Обеспечивает анализ дампа кучи, просматривая объекты в дампе кучи с помощью любого веб-браузера. По умолчанию веб-сервер запускается на порту 7000. jhat поддерживает широкий спектр предварительно разработанных запросов и язык объектных запросов (OQL) для исследования объектов в дампах кучи. - JOverflow для Java Mission Control — экспериментальный плагин, который позволяет Java Mission Control выполнять простой анализ дампа кучи и сообщать, где память может быть потрачена впустую.
- Yourkit Коммерческий профилировщик Java с анализатором дампа кучи и почти всеми функциями, предлагаемыми другими инструментами. Кроме того, YourKit предлагает:
- Область достижимости: он имеет возможность не только перечислять доступные и недостижимые объекты, но также может распределять объекты в соответствии с их областью достижимости, то есть сильно достижимыми, слабо / мягко достижимыми или недоступными.
- Проверка памяти: Вместо возможностей специальных запросов YourKit предлагает исчерпывающий набор встроенных запросов, которые могут проверять память в поисках антишаблонов и предоставлять причины и решения для обычных проблем с памятью.
Инструмент командной строки
Плагин
Я довольно часто использую Eclipse MAT и считаю его очень полезным при анализе дампов кучи.
MAT обогащен расширенными функциями, включая гистограммы и возможность сравнения их с другими гистограммами. Это дает четкое представление о том, что растет в памяти и сохраняет максимальное пространство в куче Java. Одна из функций, которые мне очень нравятся, — это «Объединить кратчайшие пути к корням сборщика мусора», которая помогает находить следы объектов, ответственные за сохранение непреднамеренно удерживаемых объектов.Например, в следующей цепочке ссылок объект ThreadLocalDateFormat удерживается в куче полем «значение» объекта ThreadLocalMap $ Entry. Пока запись ThreadLocalMap $ не будет удалена из ThreadLocalMap, ThreadLocalDateFormat не будет собираться.
weblogic.work.ExecuteThread @ 0x6996963a8 [АКТИВНЫЙ] ExecuteThread: '203' для очереди: 'weblogic.kernel.Default (самонастройка)' Монитор занятости, поток | 1 | 176 | 40 | 10 536
'- threadLocals java.lang.ThreadLocal $ ThreadLocalMap @ 0x69c2b5fe0 | 1 | 24 | 40 | 7 560
'- таблица java.lang.ThreadLocal $ ThreadLocalMap $ Entry [256] @ 0x6a0de2e40 | 1 | 1,040 | 40 | 7 536
'- [116] java.lang.ThreadLocal $ ThreadLocalMap $ Entry @ 0x69c2ba050 | 1 | 32 | 40 | 1,088
'- значение weblogic.utils.string.ThreadLocalDateFormat @ 0x69c23c418 | 1 | 40 | 40 | 1,056
При таком подходе мы можем найти корни лучших производителей в нашей куче и добраться до того, что утекает в памяти.
Java Mission Control
Java Mission Control находится в папке
OutOfMemoryError из-за завершения
Ошибка OutOfMemoryError также может быть вызвана чрезмерным использованием финализаторов.Объекты с финализатором (то есть методом finalize ()) могут задерживать восстановление занимаемого ими пространства. Поток финализатора должен вызвать метод finalize () экземпляров, прежде чем эти экземпляры могут быть восстановлены и их пространство кучи будет освобождено. Если поток финализатора не успевает за скоростью, с которой объекты становятся доступными для финализации (добавляются в очередь финализаторов, чтобы он мог вызывать их метод finalize ()), JVM может выйти из строя с OutOfMemoryError, даже если объекты накапливаются. в очереди финализатора были допущены к сбору.Поэтому очень важно убедиться, что у нас не заканчивается память из-за большого количества объектов, ожидающих завершения.
Мы можем использовать следующие инструменты для отслеживания количества объектов, ожидающих завершения:
Мы можем подключить JConsole к запущенному процессу и отслеживать количество объектов, ожидающих завершения на странице сводки виртуальной машины, как показано на следующем рисунке.
D: \ tests \ GC_WeakReferences> jmap -finalizerinfo 29456
Присоединение к процессу с идентификатором 29456, подождите...
Отладчик успешно подключен. Обнаружен компилятор сервера.
Версия JVM - 25.122-b08.
Количество объектов, ожидающих доработки: 10
Почти все инструменты анализа дампа кучи показывают подробную информацию об объектах, которые должны быть завершены.
Вывод из Java VisualVM
Дата съемки: Пт, 6 января, 14:48:54 PST 2017
Файл: D: \ tests \ java_pid19908.hprof
Размер файла: 11.3 МБ
Всего байт: 10,359,516
Всего классов: 466
Всего экземпляров: 105 182
Загрузчики классов: 2
Корни GC: 419
Количество объектов, ожидающих доработки: 2
java.lang.OutOfMemoryError: PermGen space
Как мы знаем, PermGen был удален в Java 8, поэтому, если вы работаете на Java 8 или более поздней версии, не стесняйтесь пропустить этот раздел.
Вплоть до Java 7 PermGen (сокращение от «постоянная генерация») использовался для хранения определений классов и их метаданных. Неожиданный рост PermGen или OutOfMemoryError в этом пространстве памяти означал, что либо классы не выгружаются должным образом, либо указанный размер PermGen слишком мал для размещения всех загруженных классов и их метаданных.
Чтобы гарантировать, что размер PermGen соответствует требованиям приложения, мы должны отслеживать его использование и соответствующим образом настраивать его, используя следующие параметры JVM:
–XX: PermSize = n –XX: MaxPermSize = m
Пример OutOfMemoryError для MetaSpace:
java.lang.OutOfMemoryError: Metaspace
Начиная с Java 8, метаданные класса хранятся в Metaspace. Metaspace не является частью кучи Java и выделяется из собственной памяти.Таким образом, он неограничен и ограничен только объемом встроенной памяти, доступной на машине. Однако размер метапространства можно ограничить с помощью параметра MaxMetaspaceSize
.
Мы можем столкнуться с ошибкой OutOfMemoryError для Metaspace, если и когда его использование достигнет максимального предела, указанного в MaxMetaspaceSize
. Как и в случае с другими пространствами, это может быть связано с неправильным размером Metaspace или утечкой загрузчика классов / классов. В следующем разделе мы рассмотрим инструменты диагностики, которые можно использовать для устранения утечек памяти в Metaspace.
java.lang.OutOfMemoryError: сжатое пространство классов
Если UseCompressedClassesPointers
включен (что по умолчанию, если включено UseCompressedOops), то для классов и их метаданных используются две отдельные области собственной памяти. В UseCompressedClassesPointers
64-битные указатели классов представлены 32-битными значениями, и эти сжатые указатели классов хранятся в сжатом пространстве классов. По умолчанию размер сжатого пространства классов составляет 1 ГБ, и его можно настроить с помощью CompressedClassSpaceSize.
MaxMetaspaceSize
устанавливает верхний предел общего зафиксированного размера обеих этих областей — выделенного пространства сжатого пространства класса и метаданных класса.
Пример вывода журналов GC с включенными UseCompressedClassesPointers. Зарегистрированные и зарезервированные пространства, указанные для Metaspace, включают подтвержденное и зарезервированное пространство для сжатого пространства класса.
Metaspace использовано 2921K, емкость 4486K, передано 4864K, зарезервировано 1056768K
используемое пространство класса 288K, емкость 386K, выделено 512K, зарезервировано 1048576K
PermGen и Metaspace: инструменты сбора и анализа данных
Заполнение
PermGen и Metaspace можно отслеживать с помощью Java Mission Control, Java VisualVM и JConsole.Журналы GC могут помочь нам понять использование PermGen / Metaspace до и после полных GC, а также увидеть, вызываются ли какие-либо полные GC из-за переполнения PermGen / Metaspace.
Также очень важно убедиться, что классы выгружаются тогда, когда они этого ожидают. Загрузку и выгрузку классов можно отследить с помощью:
-XX: + TraceClassUnloading –XX: + TraceClassLoading
Важно знать о синдроме, при котором приложения непреднамеренно продвигают некоторые варианты JVM от разработчика к продукту, что приводит к пагубным последствиям.Один из таких параметров — -Xnoclassgc, который указывает JVM не выгружать классы во время сборки мусора. Теперь, если приложению необходимо загрузить большое количество классов или во время выполнения какой-то набор классов становится недоступным, и оно загружает другой набор новых классов, а приложение работает с –Xnoclassgc
, тогда оно рискует достигает максимальной емкости PermGen / Metaspace и, следовательно, терпит неудачу с OutOfMemoryError. Итак, если вы не уверены, почему была указана эта опция, рекомендуется удалить ее и позволить сборщику мусора выгружать классы всякий раз, когда они подходят для сбора.
Количество загруженных классов и используемой ими памяти можно отслеживать с помощью Native Memory Tracker (NMT). Мы обсудим этот инструмент подробно ниже, в разделе «OutOfMemoryError: собственная память».
Обратите внимание, что для Concurrent MarkSweep Collector (CMS) необходимо включить следующий параметр, чтобы классы выгружались с одновременным циклом сбора данных CMS: –XX: + CMSClassUnloadingEnabled
В Java 7 значение этого флага по умолчанию — false, тогда как в Java 8 он включен по умолчанию.
jmap
«jmap –permstat» представляет статистику загрузчика классов, например, загрузчики классов, количество классов, загруженных этими загрузчиками классов, и то, живы ли эти загрузчики классов. Он также сообщает нам общее количество интернированных строк, присутствующих в PermGen, и количество байтов, занятых загруженными классами и их метаданными. Вся эта информация очень полезна для определения того, что может заполнять PermGen. Вот образец распечатки, отображающей всю эту статистику.Вы можете увидеть сводку в последней строке списка.
$ jmap -permstat 29620
Присоединение к процессу с ID 29620, подождите ...
Отладчик успешно подключен. Обнаружен клиентский компилятор.
Версия JVM - 24.85-b06.
12674 внутренних строки, занимающие 1082616 байт. поиск экземпляров загрузчика классов ..
сделано. вычислений на статистику загрузчика .. выполнено. подождите .. вычисление живучести ......................................... готово.
class_loader классы байтов parent_loader живы? тип
1846 5321080 null live <внутренний>
0xd0bf3828 0 0 null live sun / разное / Launcher $ ExtClassLoader @ 0xd8c98c78
0xd0d2f370 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0c99280 1 1440 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0b71d90 0 0 0xd0b5b9c0 live java / util / ResourceBundle $ RBClassLoader @ 0xd8d042e8
0xd0d2f4c0 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0b5bf98 1920 0xd0b5bf38 мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0c99248 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f488 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0b5bf38 6 11832 0xd0b5b9c0 мертвое солнце / отражение / разное / MethodUtil @ 0xd8e8e560
0xd0d2f338 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f418 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f3a8 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0b5b9c0 317 1397448 0xd0bf3828 live sun / misc / Launcher $ AppClassLoader @ 0xd8cb83d8
0xd0d2f300 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f3e0 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0ec3968 1 1440 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0e0a248 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0c99210 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f450 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0d2f4f8 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
0xd0e0a280 1904 null мертвое солнце / отражение / DelegatingClassLoader @ 0xd8c22f50
всего = 22 2186 6746816 Н / Д жив = 4, мертв = 18 Н / Д
Начиная с Java 8, jmap –clstats
.
jmap -clstats 26240
Присоединение к процессу с ID 26240, подождите...
Отладчик успешно подключен. Обнаружен компилятор сервера. Версия JVM - 25.66-b00, поиск экземпляров загрузчика классов ..done. вычислений на статистику загрузчика .. выполнено. пожалуйста, подождите .. вычисление живучести. анализ живости может быть неточным ...
class_loader классы байтов parent_loader живы? тип
513 950353 null live
0x0000000084e066d0 8 24416 0x0000000084e06740 live sun / misc / Launcher $ AppClassLoader @ 0x0000000016bef6a0
0x0000000084e06740 0 0 null live sun / misc / Launcher $ ExtClassLoader @ 0x0000000016befa48
0x0000000084ea18f0 0 0 0x0000000084e066d0 мертвый java / util / ResourceBundle $ RBClassLoader @ 0x0000000016c33930
всего = 4521 974769 Н / Д жив = 3, мертв = 1 Н / Д
Отвалы кучи
Как мы упоминали в предыдущем разделе, Eclipse MAT, jhat, Java VisualVM, JOverflow JMC plugin и Yourkit — это некоторые из инструментов, которые могут помочь анализировать дампы кучи для анализа OutOfMemoryErrors.Но дампы кучи также полезны для устранения проблем с памятью PermGen и Metaspace. Eclipse MAT предлагает очень приятную функцию под названием «Дубликаты классов», которая отображает любые классы, которые были загружены несколько раз разными экземплярами загрузчика классов. Некоторое конечное количество повторяющихся классов, загружаемых разными загрузчиками классов, может быть частью дизайна приложения. Но если они со временем будут расти, это тревожный сигнал, и это требует расследования. Это наиболее часто встречается в приложениях, размещенных на сервере приложений, которые выполняются на одном и том же базовом экземпляре JVM и не развертываются и повторно развертываются несколько раз.Если при отмене развертывания приложения не будут освобождены все ссылки на созданные им загрузчики классов, JVM не сможет выгрузить классы, загруженные этими загрузчиками классов, и новое развертывание приложения загрузит новый набор этих классов. с новым экземпляром загрузчика классов.
Этот снимок показывает, что существуют дублирующиеся копии классов, загруженных JaxbClassLoader, и это происходило из-за того, что приложение неправильно создавало новые экземпляры JAXBContext для каждой привязки классов XML к Java.
jcmd
jcmd
jcmd 2752 GC.class_stats 2752:
Индекс Super InstBytes Аннотации KlassBytes CpAll MethodCount Байт-коды MethodAll ROAll RWAll Всего ClassName
1 357 821632 536 0 352 2 13 616 184 1448 1632 java.lang.ref.WeakReference
2-1 295272 480 0 0 0 0 0 24 584 608 [Ljava.lang.Object;
3-1 214552 480 0 0 0 0 0 24 584 608 [К
4-1 120400 480 0 0 0 0 0 24 584 608 [B
5 35 78912 624 0 8712 94 4623 26032 12136 24312 36448 java.lang.String
6 35 67112 648 0 19384 130 4973 25536 16552 30792 47344 java.lang.Class
7 9 24680 560 0 384 1 10 496232 1432 1664 java.util.LinkedHashMap $ Entry
8 -1 13216 480 0 0 0 0 0 48 584 632 [Ljava.lang.String;
9 35 12032 560 0 1296 7 149 1520 880 2808 3688 java.util.HashMap $ Узел
10-1 8416 480 0 0 0 0 0 32 584 616 [Ljava.util.HashMap $ Node;
11-1 6512 480 0 0 0 0 0 24 584 608 [I
12 358 5688 720 0 5816 44 1696 8808 5920 10136 16056 java.lang.reflect.Field
13 319 4096 568 0 4464 55 3260 11496 7696 9664 17360 java.lang.Integer
14 357 3840 536 0584 3 56 496 344 1448 1792 java.lang.ref.SoftReference
15 35 3840 584 0 1424 8240 1432 1048 2712 3760 java.util.Hashtable $ Entry
16 35 2632 736 368 8512 74 2015 13512 8784 15592 24376 java.lang.Thread
17 35 2496 504 0 9016 42 2766 9392 6984 12736 19720 java.net.URL
18 35 2368 568 0 1344 8 223 1408 1024 2616 3640 java.util.concurrent.ConcurrentHashMap $ Узел
… …
577 35 0544 0 1736 3 136 616 640 2504 3144 sun.util.locale.provider.SPILocaleProviderAdapter $ 1
578 35 0 496 0 2736 8 482 1688 1328 3848 5176 sun.util.locale.provider.TimeZoneNameUtility
579 35 0528 0776 3 35 472424 1608 2032 вс.util.resources.LocaleData $ 1
580 442 0 608 0 1704 10290 1808 1176 3176 4352 sun.util.resources.OpenListResourceBundle
581 580 0 608 0760 5 70 7
1848 2312 sun.util.resources.TimeZoneNamesBundle
1724488 357208 1536 1117792 7754 311937 1527952 1014880 2181776 3196656 Итого
53,9% 11,2% 0,0% 35,0% - 9,8% 47,8% 31,7% 68,3% 100,0%
Индекс Super InstBytes Аннотации KlassBytes CpAll MethodCount Байт-коды MethodAll ROAll RWAll Всего ClassName
Из этого вывода мы можем видеть имена загруженных классов ( ClassName ), байты, занятые каждым классом (K lassBytes ), байты, занятые экземплярами каждого класса (InstBytes), количество методов в каждом классе ( MethodCount ), пространство, занимаемое байт-кодами ( ByteCodes), и многое другое.
Обратите внимание, что в Java 8 эта диагностическая команда требует, чтобы процесс Java запускался с параметром ‑XX: + UnlockDiagnosticVMOptions.
jcmd 33984 GC.class_stats 33984:
Для команды GC.class_stats требуется -XX: + UnlockDiagnosticVMOptions
-XX: + UnlockDiagnosticVMOption не требуется в Java 9 для этой диагностической команды.
Некоторые примеры OutOfMemoryError для собственной памяти:
OutOfMemoryError из-за нехватки места подкачки:
# Среда выполнения Java обнаружила фатальную ошибку:
#
# Ява.lang.OutOfMemoryError: запрошено 32756 байт для ChunkPool :: allocate. Нет места для подкачки?
#
# Внутренняя ошибка (allocation.cpp: 166), pid = 2290, tid = 27 # Ошибка: ChunkPool :: allocate
OutOfMemoryError из-за нехватки памяти процесса :
# Среда выполнения Java обнаружила фатальную ошибку:
#
# java.lang.OutOfMemoryError: невозможно создать новый собственный поток
Эти ошибки ясно говорят нам, что JVM не может выделить из собственной памяти, что может быть связано с тем, что сам процесс потребляет всю внутреннюю память, или в системе есть другие процессы, которые поглощают родная память.После мониторинга использования собственной кучи с помощью pmap (или других инструментов сопоставления собственной памяти) и соответствующей настройки кучи Java, количества потоков и размеров стека, а также заботы о том, чтобы оставить достаточно места для собственной кучи, если мы обнаружим, что использование собственной кучи со временем растет, и в конечном итоге мы сталкиваемся с ошибкой OutOfMemoryError, это может быть признаком утечки собственной памяти.
Native Heap OutOfMemoryError с 64-битной JVM
При работе с 32-разрядной JVM максимальный размер процесса составляет 4 ГБ, поэтому более вероятно, что у вас закончится внутренняя память с 32-разрядными процессами Java.Однако при работе с 64-битной JVM мы получаем доступ к неограниченной памяти, и технически мы не ожидаем, что у нас никогда не закончится собственная куча. Но на самом деле это не так, и нередко можно наблюдать ошибки OutOfMemoryErrors собственной кучи, возникающие и в 64-битной JVM. Это связано с тем, что в 64-разрядной JVM по умолчанию включена функция CompressedOops, и реализация этой функции определяет, где в адресном пространстве должна быть размещена куча Java. Положение кучи Java может ограничить максимальную емкость собственной памяти.Следующая карта памяти показывает, что Java Heap выделяется на границе адреса 8 ГБ, оставляя около 4 ГБ пространства для собственной кучи. Если это приложение интенсивно выделяет из собственной памяти и требует более 4 ГБ, оно вызовет ошибку OutOfMemoryError из собственной кучи, даже если в системе достаточно памяти.
0000000100000000 8K r-x-- /sw/.es-base/sparc/pkg/jdk-1.7.0_60/bin/sparcv9/java
0000000100100000 8K rwx-- /sw/.es-base/sparc/pkg/jdk-1.7.0_60 / bin / sparcv9 / java
0000000100102000 56K rwx-- [куча]
0000000100110000 2624K rwx-- [куча] <--- собственная куча
00000001FB000000 24576K rw --- [anon] <--- Java Heap начинается здесь
0000000200000000 1396736K rw --- [анон]
0000000600000000 700416K rw --- [анон]
Эту проблему можно решить, используя параметр -XX: HeapBaseMinAddress = n, чтобы указать адрес, с которого должна начинаться куча Java. Установка более высокого адреса оставит больше места для собственной кучи.
Дополнительные сведения о диагностике, устранении неполадок и обходе этой проблемы см. Здесь:
Собственная куча: средства диагностики
Давайте взглянем на инструменты обнаружения утечек памяти, которые могут помочь нам найти причину утечек собственной памяти.
Отслеживание собственной памяти
JVM имеет мощную функцию под названием Native Memory Tracking (NMT), которую можно использовать для отслеживания собственной памяти, которая используется внутри JVM. Обратите внимание, что он не может отслеживать память, выделенную вне JVM или собственными библиотеками.Используя следующие два простых шага, мы можем отслеживать использование собственной памяти JVM:
- Запустить процесс с включенным NMT. Уровень вывода может быть установлен на уровень «сводка» или «детализация»:
- -XX: NativeMemoryTracking = сводка
- -XX: NativeMemoryTracking = деталь
- Используйте jcmd для получения сведений об использовании собственной памяти:
- jcmd
VM.native_memory
- jcmd
Пример вывода NMT:
d: \ tests> jcmd VM.native_memory :
Отслеживание собственной памяти:
Итого: зарезервировано = 3431296 КБ, зафиксировано = 2132244 КБ
- Java Heap (зарезервировано = 2017280 КБ, зафиксировано = 2017280 КБ)
(mmap: зарезервировано = 2017280 КБ, зафиксировано = 2017280 КБ)
- Класс (зарезервировано = 1062088 КБ, зафиксировано = 10184 КБ)
(классы №411)
(malloc = 5320 КБ, # 190)
(mmap: зарезервировано = 1056768 КБ, зафиксировано = 4864 КБ)
- Тема (зарезервировано = 15423 КБ, зафиксировано = 15423 КБ)
(поток # 16)
(стек: зарезервировано = 15360 КБ, зафиксировано = 15360 КБ)
(malloc = 45 КБ # 81)
(arena = 18KB # 30)
- Код (зарезервировано = 249658 КБ, зафиксировано = 2594 КБ)
(malloc = 58 КБ # 348)
(mmap: зарезервировано = 249600 КБ, зафиксировано = 2536 КБ)
- GC (зарезервировано = 79628 КБ, зафиксировано = 79544 КБ)
(malloc = 5772 КБ # 118)
(mmap: зарезервировано = 73856 КБ, зафиксировано = 73772 КБ)
- Компилятор (зарезервировано = 138 КБ, зафиксировано = 138 КБ)
(malloc = 8 КБ # 41)
(arena = 131KB # 3)
- Внутренний (зарезервировано = 5380 КБ, зафиксировано = 5380 КБ)
(malloc = 5316 КБ # 1357)
(mmap: зарезервировано = 64 КБ, зафиксировано = 64 КБ)
- Символ (зарезервировано = 1367 КБ, зафиксировано = 1367 КБ)
(malloc = 911 КБ # 112)
(arena = 456KB # 1)
- Отслеживание собственной памяти (зарезервировано = 118 КБ, зафиксировано = 118 КБ)
(malloc = 66 КБ # 1040)
(накладные расходы на отслеживание = 52 КБ)
- Arena Chunk (зарезервировано = 217 КБ, зафиксировано = 217 КБ)
(malloc = 217 КБ)
Более подробную информацию обо всех командах jcmd для доступа к данным NMT и о том, как читать их вывод, можно найти здесь.
Средства обнаружения утечек встроенной памяти
В случае утечек собственной памяти, происходящих извне JVM, нам нужно полагаться на инструменты утечки собственной памяти для их обнаружения и устранения неполадок. Нативные инструменты, такие как dbx, libumem, valgrind, purify и т. Д., Могут спасти нас при работе с внешними утечками внутренней памяти JVM.
Сводка
Устранение проблем с памятью может быть очень сложным и сложным делом, но правильный подход и правильный набор инструментов определенно упрощают и упрощают их решение.Как мы видели, Java HotSpot JVM сообщает о различных типах сообщений OutOfMemoryError, и очень важно четко понимать эти сообщения об ошибках и иметь широкий спектр инструментов диагностики и устранения неполадок в нашем наборе инструментов для диагностики и устранения этих ошибок. проблемы.
Об авторе
Пунам Пархар , в настоящее время инженер по поддержке JVM в Oracle, где ее основная обязанность заключается в решении проблем, возникающих у клиентов в отношении JVM JRockit и HotSpot.Она любит отладку и устранение неполадок, и всегда сосредоточена на улучшении удобства обслуживания и поддержки JVM. Она решила множество сложных проблем со сборкой мусора в JVM HotSpot и увлечена улучшением инструментов отладки и удобства обслуживания продукта, чтобы упростить поиск и устранение неполадок, связанных со сборщиком мусора, в JVM. Пытаясь помочь клиентам и сообществу Java, она делится своим опытом и знаниями в блоге, который ведет здесь.
Monitor Управление памятью Java с помощью показателей времени выполнения, APM и журналов
Виртуальная машина Java (JVM) динамически управляет памятью для ваших приложений, гарантируя, что вам не нужно вручную выделять и освобождать память в коде. Но любой, кто когда-либо сталкивался с исключением java.lang.OutOfMemoryError
, знает, что этот процесс может быть несовершенным - вашему приложению может потребоваться больше памяти, чем может выделить JVM. Или, когда JVM выполняет сборку мусора для освобождения памяти, она может создавать чрезмерно длинные паузы в активности приложения, что приводит к замедлению работы ваших пользователей.В этом посте мы рассмотрим, как JVM управляет памятью кучи с помощью сборок мусора, а также рассмотрим некоторые ключевые показатели и журналы, которые обеспечивают видимость управления памятью JVM. Затем мы рассмотрим коррелирующие метрики, трассировки и журналы, чтобы собрать больше контекста об ошибках, связанных с нехваткой памяти, и показать вам, как настроить оповещения для отслеживания проблем, связанных с памятью, с помощью Datadog.
При запуске JVM запрашивает память для кучи, области памяти, которую JVM использует для хранения объектов, к которым потоки вашего приложения должны получить доступ.Этот начальный размер кучи настраивается с помощью флага -Xms
. JVM будет динамически выделять память вашему приложению из кучи, вплоть до максимального размера кучи (максимальный объем памяти, который JVM может выделить для кучи, настраивается с помощью флага -Xmx
). JVM автоматически выбирает начальный и максимальный размеры кучи в зависимости от емкости ресурсов физического хоста, если вы не укажете иное. Если ваше приложение использует кучу максимального размера, но ему по-прежнему требуется больше памяти, оно сгенерирует исключение OutOfMemoryError
.
Вы можете явно настроить начальный и максимальный размер кучи с помощью флагов -Xms
и -Xmx
(например, -Xms 50m -Xmx 100g
установит минимальный размер кучи 50 МБ и максимальный размер кучи 100 ГБ ).
По мере того, как ваше приложение создает объекты, JVM динамически выделяет память из кучи для хранения этих объектов, и использование кучи возрастает. На графике выше вы можете увидеть среднее использование кучи (каждая синяя или зеленая линия представляет экземпляр JVM) вместе с максимальным использованием кучи (красным).JVM также выполняет сборку мусора, чтобы освободить память от объектов, которые ваше приложение больше не использует, периодически создавая провал в использовании кучи. Это помогает гарантировать, что у JVM будет достаточно памяти для выделения вновь созданным объектам.
Алгоритмы сборщика мусора
Сборка мусора необходима для освобождения памяти, но она временно приостанавливает потоки приложений, что может привести к проблемам с задержкой, с которыми сталкивается пользователь. В идеале JVM должна запускать сборку мусора достаточно часто, чтобы освободить память, которая требуется приложению, но не так часто, чтобы она без необходимости прерывала работу приложения.Алгоритмы сборки мусора Java развивались на протяжении многих лет, чтобы сократить продолжительность пауз и максимально эффективно освободить память. Начиная с Java 9, сборщик мусора Garbage-First, или G1 GC, является сборщиком по умолчанию. Хотя другие, более эффективные сборщики мусора находятся в разработке, G1 GC в настоящее время является лучшим вариантом для готовых к работе приложений, которым требуется большой объем памяти кучи и более короткие паузы в работе приложения. Поэтому в этом посте мы остановимся на сборщике G1.
Как сборщик G1 организует кучу
G1 поровну делит кучу на регионы; каждый регион назначен либо молодому поколению , либо старому поколению . Молодое поколение состоит из райских регионов и уцелевших регионов, в то время как старое поколение состоит из старых регионов и огромных регионов (для хранения «огромных» объектов, требующих более 50 процентов памяти региона). За исключением огромных объектов, вновь выделенные объекты назначаются региону eden в молодом поколении, а затем перемещаются в более старые регионы (оставшиеся в живых или старые регионы) в зависимости от количества выживших сборок мусора.
Этапы сборки мусора G1
Во время работы Java-приложения сборщик мусора проводит инвентаризацию того, какие объекты все еще используются или на которые ссылаются («живые» объекты), а какие объекты больше не нужны («мертвые» объекты) и можно удалить из кучи. Паузы Stop-the-world (когда все действия приложений временно останавливаются) обычно происходят, когда сборщик эвакуирует живые объекты в другие регионы и сжимает их, чтобы освободить больше памяти.
Цикл сборки мусора G1 чередуется между фазой только для молодых и фазой восстановления пространства.Во время фазы «только молодые» сборщик G1 выполняет два типа процессов:
- «молодых» сборщиков мусора, которые эвакуируют живые объекты из райского уголка в регионы выживших или из оставшихся в живых в старые регионы в зависимости от их возраста.
- цикл маркировки, который включает инвентаризацию живых объектов в регионах старого поколения. G1 начинает этот процесс при подготовке к этапу освоения пространства, если обнаруживает, что определенный процент старого поколения занят.
Некоторые фазы цикла маркировки выполняются одновременно с приложением.Во время этого JVM может продолжать выделять память приложению по мере необходимости. Если сборщик мусора успешно завершает цикл маркировки, он обычно переходит в фазу восстановления пространства, где запускает несколько смешанных сборок, названных так потому, что они эвакуируют объекты из смеси молодых и старых регионов. Когда коллектор G1 определяет, что из смешанных коллекций эвакуировано достаточно регионов старого поколения без превышения целевого времени паузы (желаемая максимальная продолжительность пауз остановки мира), фаза только для молодых начинается снова.
Если, с другой стороны, у сборщика G1 слишком мало доступной памяти для завершения цикла маркировки, может потребоваться запустить полную сборку мусора. Полный сборщик мусора обычно занимает больше времени, чем сбор только для молодых или смешанная коллекция, поскольку он эвакуирует объекты по всей куче, а не в стратегически выбранных регионах. Вы можете отслеживать, как часто происходит полная сборка мусора, собирая и анализируя журналы сборки мусора, которые мы рассмотрим в следующем разделе.
Мы представили краткий (и упрощенный) обзор управления памятью JVM и исследовали, как JVM использует сборку мусора для освобождения памяти кучи, которая больше не используется.В этом разделе мы рассмотрим ключевые показатели времени выполнения JVM и журналы сборки мусора, которые помогут вам отслеживать проблемы, связанные с памятью в ваших приложениях Java.
Метрики времени выполнения JVM
JVM предоставляет метрики времени выполнения, включая информацию об использовании памяти кучи, количестве потоков и классах, через компоненты MBean. Служба мониторинга, такая как Java Agent Datadog, может запускаться непосредственно в JVM, собирать эти метрики локально и автоматически отображать их на готовой информационной панели, подобной показанной выше.С помощью распределенной трассировки и APM вы также можете сопоставить трассировки отдельных запросов с метриками JVM. Ниже вы можете увидеть время трассировки, наложенное на график каждой метрики для упрощения корреляции, что позволяет визуализировать состояние среды выполнения приложения во время медленного запроса.
Далее мы рассмотрим несколько ключевых тенденций в показателях JVM, которые могут помочь вам обнаружить проблемы с управлением памятью.
Среднее использование кучи после каждой сборки мусора неуклонно растет
Если вы заметили, что базовое использование кучи постоянно увеличивается после каждой сборки мусора, это может указывать на то, что требования вашего приложения к памяти растут или у вас есть утечка памяти ( приложение не освобождает ссылки на объекты, которые больше не нужны, непреднамеренно предотвращая их сборку мусора).В любом случае вы захотите изучить и либо выделить больше памяти кучи для вашего приложения (и / или реорганизовать логику вашего приложения, чтобы выделить меньше объектов), либо отладить утечку с помощью такой утилиты, как VisualVM или Mission Control.
Если вам нужно увеличить размер кучи, вы можете взглянуть на несколько других показателей, чтобы определить разумную настройку, которая не приведет к превышению доступных ресурсов вашего хоста. Имейте в виду, что JVM также несет некоторые накладные расходы (например, она хранит кеш кода в памяти, отличной от кучи).MBean java.lang: type = Memory
предоставляет метрики для HeapMemoryUsage
и NonHeapMemoryUsage
, чтобы вы могли учитывать комбинированное использование кучи и не кучи памяти JVM. Вы также можете сравнить использование памяти на системном уровне вашего физического сервера с использованием кучи и без кучи JVM, построив график этих показателей на одной панели.
Размер старого поколения увеличивается
JVM предоставляет метрику Usage.used
через java.lang: name = G1 Old Gen, type = MemoryPool
MBean, который измеряет объем памяти, выделенной объектам старого поколения (обратите внимание, что это включает живые и мертвых объектов, которые еще не были собраны мусором).В нормальных условиях этот показатель должен оставаться неизменным. Если вы видите неожиданное увеличение этой метрики, это может сигнализировать о том, что ваше приложение Java создает долгоживущие объекты (по мере старения объектов сборщик мусора эвакуирует их в регионы в старом поколении) или создает более громоздкие объекты (которые автоматически получают выделены регионам в старом поколении).
Процент времени, затраченного на сборку мусора
По умолчанию сборщик G1 пытается тратить около 8 процентов времени на сборку мусора (настраивается с помощью параметра XX: GCTimeRatio
).Но, как и в случае с указанным выше целевым временем паузы, JVM не может гарантировать, что сможет выполнить этот прогноз.
Вы можете отслеживать количество времени, затрачиваемого на каждую фазу сборки мусора, запрашивая метрику CollectionTime
из трех компонентов MBean, которые будут отображать время сборки только молодого, смешанного и старого (полного) мусора в миллисекундах:
-
java.lang: type = GarbageCollector, name = G1 Young Generation
-
java.lang: type = GarbageCollector, name = G1 Mixed Generation
-
java.lang: type = GarbageCollector, name = G1 Старое поколение
Чтобы оценить долю времени, затраченного на сборку мусора, вы можете использовать службу мониторинга для автоматического запроса этой метрики, преобразования ее в секунды и расчета посекундной скорости. .
Выше мы изобразили процент времени, потраченного на смешанную и полную сборку, на верхнем графике и процент времени, потраченный на сборку молодого мусора, на нижнем графике. Вы также можете соотнести процент времени, потраченного на сборку мусора, с использованием кучи, построив их график на той же панели мониторинга, как показано ниже.
Хотя показатели дают общее представление о частоте и продолжительности сборки мусора, они не всегда обеспечивают уровень детализации, необходимый для отладки проблем. Если вы заметили, что ваше приложение тратит больше времени на сборку мусора или использование кучи постоянно увеличивается даже после каждой сборки мусора, вы можете просмотреть журналы для получения дополнительной информации.
Журналы сборки мусора
Журналы предоставляют более подробную информацию об отдельных этапах сборки мусора.Они также помогают лучше понять, чем просто показатели JVM, когда ваше приложение выходит из строя из-за ошибки нехватки памяти - вы часто можете получить больше информации о том, что произошло, просмотрев журналы во время сбоя. Более того, вы можете использовать журналы для отслеживания частоты и продолжительности различных процессов, связанных со сборкой мусора: сборок только для молодых, смешанных сборок, отдельных фаз цикла маркировки и полных сборок мусора. Журналы также могут сообщить вам, сколько памяти было освобождено в результате каждого процесса сборки мусора.
Флаг -verbose: gc
настраивает JVM для регистрации этих сведений о каждом процессе сборки мусора. Начиная с Java 9, JVM Unified Logging Framework использует другой формат флагов для генерации подробных выходных данных журнала сборки мусора: -Xlog: gc *
(хотя -verbose: gc
по-прежнему работает). См. Документацию для получения подробной информации о преобразовании флагов ведения журнала сборки мусора до Java 9.x в новые флаги Xlog
.
На скриншоте выше вы можете увидеть пример подробного журнала сборки мусора.В первом поле отображается время с момента последнего запуска или перезапуска JVM (532 002,067 секунд), за которым следует уровень состояния журнала ( информация
). Выходные данные также показывают, что сборщик G1 выполнил сборку мусора только для молодых, что привело к остановке мира при эвакуации объектов в другие регионы. Сборщик мусора сократил использование кучи с 11 884 МБ ( gc.memory_before
) до 3295 МБ ( gc.memory_after
). В следующем поле ( gc.memory_total
) указывается размер кучи: 14 336 МБ.Наконец, duration
указывает количество времени, которое потребовалось на сборку мусора: 11,456 мс.
Служба управления журналами может автоматически анализировать атрибуты из ваших журналов, включая продолжительность сбора. Он также может рассчитать разницу между значениями memory_before
и memory_after
, чтобы помочь вам отслеживать объем освобожденной памяти ( gc.memory_freed
в обработанном журнале выше) каждым процессом, что позволяет проанализировать, насколько эффективно ваш сборщик мусора со временем освобождает память.Затем вы можете сравнить его с метриками JVM, такими как процент времени, потраченного на сборку мусора.
Если ваше приложение тратит большой процент времени на сборку мусора, но сборщик может успешно освобождать память, вы можете создавать множество краткосрочных выделений (часто создавая объекты, а затем освобождая ссылки на них). Чтобы сократить время, затрачиваемое на сборку мусора, вы можете уменьшить количество выделений, требуемых вашему приложению, просмотрев текущие выделения с помощью такого инструмента, как VisualVM.
На этих графиках вы можете видеть, что это приложение тратит больший процент времени на выполнение молодых коллекций (вверху) и что процессы сборки мусора освобождают постоянный объем памяти с течением времени (внизу).
С другой стороны, если ваше приложение тратит больше времени на сборку мусора и , эти сборки мусора со временем освобождают меньше памяти, это может указывать на то, что вы создаете более долгоживущие объекты (объекты, которые долго находятся в куче). периоды времени и, следовательно, не могут быть собраны мусором).В этом случае вы можете либо попытаться уменьшить объем памяти, который требуется вашему приложению, либо увеличить размер кучи, чтобы избежать возникновения ошибки нехватки памяти.
На этих графиках вы можете видеть, что это приложение тратит больший процент времени на выполнение молодых коллекций (вверху) и что процессы сборки мусора со временем освобождают меньше памяти (внизу).
Помимо использования журналов для отслеживания эффективности и частоты процессов сборки мусора, вы также можете следить за журналами, которые указывают на то, что ваша JVM изо всех сил пытается справиться с требованиями вашего приложения к памяти.Ниже мы подробно рассмотрим два примечательных журнала:
- «To-space исчерпано»
- Full GC или полные сборки мусора
To-space исчерпаны
Если ваша куча находится под давлением, а сборка мусора не выполняется Если вы не можете восстановить память достаточно быстро, чтобы удовлетворить потребности вашего приложения, в ваших журналах может появиться сообщение «To-space is made» Это указывает на то, что сборщику мусора не хватает «свободного места» или свободного места для эвакуации объектов в другие регионы.Если вы видите этот журнал, это обычно означает, что сборщику вскоре потребуется запустить полную сборку мусора.
Полная сборка мусора
Сборщику G1 иногда требуется запустить полную сборку мусора, если он не справляется с требованиями вашего приложения к памяти. Другие типы коллекций стратегически нацелены на определенные регионы в попытке достичь цели по времени паузы. Напротив, полная сборка мусора обычно занимает больше времени (что приводит к более длительным паузам в работе приложения), потому что сборщику G1 требуется освободить память во всей куче.
В журнале ниже вы можете видеть, что эта полная сборка мусора смогла освободить 2620 МБ памяти, но это также заняло почти пять секунд (продолжительность
). В это время приложение не могло выполнять какую-либо работу, что приводило к высокой задержке запроса и низкой производительности.
Полная сборка мусора обычно происходит, когда сборщику не хватает памяти для завершения фазы цикла маркировки. Если это произойдет, вы можете увидеть журнал [GC concurrent-mark-start]
, который указывает на начало фазы параллельной маркировки цикла маркировки, за которым следует журнал Full GC (Allocation Failure)
, который запускает полный сборка мусора из-за того, что для цикла маркировки не хватило памяти для продолжения.Вскоре после этого вы увидите журнал [GC concurrent-mark-abort]
, который подтверждает, что сборщик был вынужден отказаться от цикла маркировки:
Еще одним фактором, способствующим полному сборке мусора, является размещение огромных объектов. Огромные объекты выделяются непосредственно старому поколению и занимают больше памяти, чем обычные объекты. Если ваше приложение запрашивает выделение памяти для огромных объектов, это увеличивает вероятность того, что сборщику G1 потребуется выполнить полную сборку мусора.
Огромным объектам иногда может потребоваться память более чем в одной области, что означает, что сборщику необходимо выделить память из соседних областей. Это может привести к тому, что JVM запустит полную сборку мусора (даже если у нее достаточно памяти для распределения по разным регионам), если это единственный способ освободить необходимое количество непрерывных областей для хранения каждого огромного объекта.
Если вы заметили, что ваше приложение выполняет больше полных сборок мусора, это сигнализирует о том, что JVM сталкивается с большой нехваткой памяти, и приложение может столкнуться с ошибкой нехватки памяти, если сборщик мусора не может восстановить достаточно памяти чтобы удовлетворить его потребности.Служба мониторинга производительности приложений, такая как Datadog, может помочь вам исследовать ошибки нехватки памяти, позволяя просматривать полную трассировку стека в трассировке запроса (как показано ниже) и переходить к связанным журналам и метрикам времени выполнения для получения дополнительной информации.
Сбор и сопоставление журналов приложений и журналов сборки мусора на одной платформе позволяет увидеть, возникли ли ошибки нехватки памяти примерно в то же время, что и полная сборка мусора. В потоке журнала ниже похоже, что сборщику мусора G1 не хватило доступной памяти кучи для продолжения цикла маркировки ( concurrent-mark-abort
), поэтому ему пришлось запустить полную сборку мусора ( Full GC Allocation Failure
).Примерно в это же время приложение генерировало ошибку нехватки памяти ( java.lang.OutOfMemoryError: Java heap space
), указывая на то, что это давление памяти в куче влияло на производительность приложения.
Регулярный мониторинг способности JVM эффективно управлять памятью и выделять ее имеет решающее значение для обеспечения бесперебойной работы ваших Java-приложений. В следующем разделе мы рассмотрим, как настроить оповещения для автоматического отслеживания проблем управления памятью JVM и производительности приложений.
Аномальный рост использования кучи указывает на то, что сборка мусора не может удовлетворить потребности вашего приложения в памяти, что может привести к задержкам приложения и ошибкам нехватки памяти. В Datadog вы можете настроить пороговое оповещение, чтобы автоматически получать уведомления, когда среднее использование кучи превышает 80 процентов от максимального размера кучи.
Если вы получили это уведомление, вы можете попробовать увеличить максимальный размер кучи или выяснить, можете ли вы изменить логику своего приложения, чтобы выделить меньше долгоживущих объектов.
Поскольку сборщик G1 выполняет часть своей работы одновременно, более высокая скорость работы по сбору мусора не обязательно является проблемой, если только он не вводит длительные паузы с остановкой, которые коррелируют с задержкой приложения, с которой сталкивается пользователь. Datadog APM предоставляет предупреждения, которые можно включить одним нажатием кнопки, если вы хотите сразу же автоматически отслеживать определенные ключевые показатели. Например, вы можете включить предлагаемое предупреждение, которое уведомляет вас, когда задержка 90-го процентиля для пользовательских запросов к вашему Java-приложению (служба : java-pet-clinic
в данном случае) превышает пороговое значение или когда увеличивается частота ошибок.
Если вы получили предупреждение, вы можете перейти к медленным трассировкам в APM и сопоставить их с метриками JVM (такими как процент времени, потраченного на сборку мусора), чтобы узнать, может ли задержка быть связана с проблемами управления памятью JVM.
JVM автоматически работает в фоновом режиме, освобождая память и эффективно распределяя ее в соответствии с изменяющимися требованиями вашего приложения к ресурсам. Алгоритмы сборки мусора стали более эффективными в сокращении остановок в работе приложений, но они не могут гарантировать защиту от ошибок нехватки памяти.Вам нужна полная видимость всего вашего приложения и его среды выполнения JVM, чтобы эффективно устранять ошибки, связанные с нехваткой памяти, и обнаруживать проблемы, связанные с управлением памятью, до того, как эти ошибки даже произойдут.
Если вы используете Datadog APM для мониторинга производительности вашего Java-приложения, вы можете сопоставить данные о производительности приложения, трассировки запросов, метрики времени выполнения JVM и журналы сборки мусора, чтобы выяснить, связан ли всплеск задержки с проблемой управления памятью. (е.g., вам нужно увеличить кучу или пересмотреть ваше приложение, чтобы выделить меньше объектов?) или другой тип узкого места.
Встроенная утилита JMXFetch агента Datadog Agent запрашивает у MBean-компонентов ключевые показатели, такие как использование кучи, время сборки мусора и размер старого поколения. А Java-клиент Datadog APM обеспечивает глубокую визуализацию производительности приложений, автоматически отслеживая запросы между фреймворками и библиотеками в экосистеме Java, включая Tomcat, Spring и соединения с базами данных через JDBC.
Чтобы узнать больше о функциях мониторинга Java Datadog, ознакомьтесь с документацией. Если вы новичок в Datadog и хотите следить за работоспособностью и производительностью своих Java-приложений, подпишитесь на бесплатную пробную версию, чтобы начать работу.
Анализ памяти java | Dynatrace
Цель любого анализа памяти Java - оптимизировать сборку мусора (GC) таким образом, чтобы свести к минимуму ее влияние на время отклика приложения или использование ЦП. Не менее важно обеспечить стабильность работы приложения.Нехватка и утечки памяти часто приводят к нестабильности. Чтобы идентифицировать нестабильность, вызванную памятью, или чрезмерную сборку мусора, нам сначала нужно отслеживать наше Java-приложение с помощью соответствующих инструментов. Если сборка мусора отрицательно влияет на время отклика, нашей целью должна быть оптимизация конфигурации. Целью каждого изменения конфигурации должно быть уменьшение этого воздействия. Наконец, если одних изменений конфигурации недостаточно, мы должны проанализировать шаблоны распределения и само использование памяти. Итак, приступим к делу.
Начиная с Java 5, стандартным инструментом мониторинга JDK была JConsole. Oracle JDK также включает jStat, который позволяет отслеживать использование памяти и активность сборщика мусора с консоли, и Java VisualVM (или jvisualvm), который обеспечивает элементарный анализ памяти и профилировщик. Oracle JRockit JDK включает JRockit Mission Control и флаг verbose: gc JVM. Каждый поставщик JVM включает в себя свои собственные инструменты мониторинга, и существует множество доступных коммерческих инструментов, предлагающих дополнительные функции.
Мониторинг использования памяти и активности ГХ
Нехватка памяти часто является причиной нестабильности и зависания приложений Java. Следовательно, нам необходимо отслеживать влияние сборки мусора на время отклика и использование памяти, чтобы обеспечить как стабильность, так и производительность. Однако мониторинга использования памяти и времени сборки мусора недостаточно, поскольку эти два элемента сами по себе не говорят нам, влияет ли сборка мусора на время отклика приложения.Только приостановка сборки мусора напрямую влияет на время отклика, и сборщик мусора также может работать одновременно с приложением. Поэтому нам необходимо соотнести приостановки, вызванные сборкой мусора, со временем отклика приложения. Исходя из этого, нам необходимо отслеживать следующее:
- Использование различных пулов памяти (Eden, сохранившаяся и старая). Нехватка памяти - главная причина повышенной активности сборщика мусора.
- Если общее использование памяти постоянно увеличивается, несмотря на сборку мусора, происходит утечка памяти, которая неизбежно приводит к ошибке нехватки памяти.В этом случае необходим анализ кучи памяти.
- Количество коллекций молодого поколения предоставляет информацию о скорости оттока (скорость распределения объектов). Чем выше число, тем больше объектов размещается. Большое количество молодых коллекций может быть причиной проблемы времени отклика и растущего старшего поколения (потому что молодое поколение больше не может справляться с количеством объектов).
- Если использование старого поколения сильно колеблется, не увеличиваясь после сборки мусора, то объекты без надобности копируются из молодого поколения в старое.Для этого есть три возможных причины: молодое поколение слишком мало, высокий уровень оттока клиентов или слишком большое использование транзакционной памяти.
- Высокая активность сборщика мусора обычно отрицательно сказывается на использовании ЦП. Однако только приостановки (также известные как «Stop the World Events») имеют прямое влияние на время отклика. Вопреки распространенному мнению, приостановка не ограничивается крупными сборщиками мусора. Поэтому важно отслеживать приостановки в зависимости от времени отклика приложения.
Панель мониторинга памяти JVM (рисунок 2.11) показывает, что постоянное (или старое) поколение постоянно растет, только чтобы вернуться к своему старому уровню после GC старого поколения (внизу справа). Это означает, что утечки памяти нет, а причина роста - преждевременное владение объектами. Молодое поколение слишком мало, чтобы справиться с распределением текущих транзакций. На это также указывает большое количество GC молодого поколения (Oracle / Sun Copy GC). Эти так называемые второстепенные сборщики мусора часто игнорируются и считаются не имеющими никакого значения.
JVM будет приостановлена на время выполнения второстепенной сборки мусора; это событие остановки мира. Второстепенные сборщики мусора обычно довольно быстрые, поэтому их называют второстепенными, но в этом случае они сильно влияют на время отклика. Основная причина та же, что уже упоминалось: молодое поколение слишком маленькое, чтобы справиться. Важно отметить, что этого может быть недостаточно для увеличения численности молодого поколения. Более крупное молодое поколение может разместить больше живых объектов, что, в свою очередь, приведет к более длительным циклам сборки мусора.Лучшая оптимизация - всегда уменьшать количество выделений и общие требования к памяти.
Рисунок 2.11: Это показывает, как можно визуализировать активность памяти JVM
Конечно, мы не можем избежать циклов сборки мусора, да и не хотели бы. Однако мы можем оптимизировать конфигурацию, чтобы минимизировать влияние приостановки ГХ на время отклика.
Как отслеживать и интерпретировать влияние ГХ на время отклика
Приостановка сборщика мусора представляет собой единственное прямое влияние на время отклика сборщика мусора.Единственный способ контролировать это - через интерфейс инструмента JVM (JVM TI), который можно использовать для регистрации обратных вызовов в начале и в конце приостановки. Во время события stop-the-world все активные транзакции приостанавливаются. Мы можем соотнести эти приостановки с транзакциями приложений, определив как начало, так и конец приостановки. Фактически, мы можем сказать, как долго конкретная транзакция была приостановлена сборщиком мусора. (См. Рисунок 2.12.)
Рисунок 2.12: Две полосы представляют продолжительность транзакции с приостановкой сборки мусора и без нее.Это означает, что разница отражает влияние сборки мусора
на производительность.
Только несколько инструментов позволяют осуществлять прямой мониторинг приостановки, и Dynatrace является одним из них. Если у вас нет такого инструмента, вы можете использовать jStat, JConsole или аналогичный инструмент для отслеживания времени сборки мусора. Показатели, сообщаемые в jStat, также напрямую предоставляются JVM через JMX. Это означает, что вы можете использовать любое решение для мониторинга с поддержкой JMX для отслеживания этих показателей.
Важно различать GC молодого и старого поколения (или второстепенный и большой, как их иногда называют; подробнее см. В следующем разделе), и не менее важно понимать как частоту, так и продолжительность циклов GC.ГХ молодого поколения будут в основном непродолжительными, но при большой нагрузке могут быть очень частыми. Многие быстрые сборщики мусора могут быть такими же плохими с точки зрения производительности, как и один долговечный (помните, что сборщики мусора молодого поколения всегда останавливают мир).
Частые ГХ молодого поколения имеют две основные причины:
- Слишком маленькое молодое поколение для нагрузки приложения.
- Высокий отток
Если молодое поколение слишком мало, мы видим растущее старое поколение из-за преждевременного владения объектами.
Если слишком много объектов выделяется слишком быстро (т. Е. При высоком уровне оттока), молодое поколение заполняется так же быстро, и должен запускаться сборщик мусора. Хотя сборщик мусора все еще может справиться с переполнением данных старого поколения, он должен делать это за счет производительности приложения.
Высокий уровень оттока может помешать нам когда-либо достичь оптимального размера генерации, поэтому мы должны исправить такую проблему в нашем коде, прежде чем пытаться оптимизировать саму сборку мусора.Существует относительно простой способ определить проблемные области кода.
Приостановки времени выполнения, показанные на рис. 2.13, сообщают о значительной статистической концентрации сборок мусора в одной конкретной функции. Это очень необычно, потому что сборщик мусора запускается не конкретным методом, а скорее состоянием заполнения кучи. Тот факт, что один метод приостанавливается чаще, чем другие, предполагает, что сам метод выделяет достаточно объектов, чтобы заполнить молодое поколение и, таким образом, запускает сборщик мусора.Каждый раз, когда мы видим такую статистическую аномалию, мы находим главного кандидата для анализа и оптимизации распределения (которые мы рассмотрим далее в разделе «Анализ распределения» этой главы ниже).
Рисунок 2.13: Показывает, как часто и как долго определенный метод был приостановлен сборщиком мусора
Основные и второстепенные сборки мусора
То, что я называл сборщиками мусора молодого и старого поколения, обычно называют второстепенными и основными сборщиками мусора.Точно так же общеизвестно, что основные сборщики мусора приостанавливают вашу JVM, плохо сказываются на производительности и по возможности их следует избегать. С другой стороны, второстепенные сборщики мусора часто считаются не имеющими значения и игнорируются во время мониторинга. Как уже объяснялось, второстепенные сборщики мусора приостанавливают работу приложения, и ни их, ни основные сборщики мусора не следует игнорировать. Главный сборщик мусора часто приравнивается к сборке мусора в старом поколении; Однако это не совсем так. Хотя каждый крупный сборщик мусора собирает старое поколение, не каждый сборщик мусора старого поколения является крупной коллекцией.Следовательно, причина, по которой мы должны минимизировать или избегать крупных сборщиков мусора, неправильно понимается. Глядя на вывод verbose: GC объясняет, что я имею в виду:
[GC 325407K-> 83000K (776768K), 0,2300771 с]
[GC 325816K-> 83372K (776768K), 0,2454258 с]
[Полный GC 267628K-> 83769K (776768K), 1,8479984 с]
Основной GC - это полный GC. Главный сборщик мусора собирает все области кучи, включая молодое поколение и, в случае Oracle HotSpot JVM, постоянное поколение.Кроме того, он делает это во время события остановки мира, что означает, что приложение приостанавливается на длительный период времени, часто на несколько секунд или минут. С другой стороны, у нас может быть много GC-активности в старом поколении, но мы никогда не увидим большой (полный) GC или не будем иметь длительных приостановок, вызванных GC. Чтобы убедиться в этом, просто запустите любое приложение с параллельным сборщиком мусора. Используйте jstat — gcutil для отслеживания поведения GC вашего приложения.
Об этом выводе сообщает jstat -gcutil.Первые пять столбцов в таблице 2.1 показывают использование различных пространств в процентах - оставшийся в живых 0, оставшийся в живых 1, Эдем, старый и постоянный - с последующим количеством активаций GC и их накопленным временем - молодые активации GC и время, полное Активации GC и время. В последнем столбце показано общее время, затраченное на сборку мусора. Третья строка показывает, что старое поколение сократилось, а jStat сообщил о полном сборке мусора (см. Числа, выделенные жирным шрифтом).
** S0 ** ** S1 ** ** E ** ** O ** ** P ** ** YGC ** ** YGCT ** ** FGC ** ** FGCT ** ** GCT **
90.68 0,00 32,44 85,41 45,03 134 15,270 143 21,683 36,954
90,68 0,00 32,45 85,41 45,03 134 15,270 143 21,683 36,954
90,68 0,00 38,23 _ ** 85,41 ** _ 45,12 134 15,270 _ ** 143 ** _ _ ** 21,683 ** _ _ ** 36,954 ** _
90,68 0,00 38,52 _ ** 85,39 ** _ 45,12 134 15,270 _ ** 144 ** _ _ ** 21,860 ** _ _ ** 37,131 ** _
90,68 0,00 38,77 85,39 45,12 134 15,270 144 21,860 37,131
90,68 0,00 49,78 85,39 45,13 134 15,270 145 21,860 37,131
Таблица 2.1: jstat -gcutil выходной отчет
С другой стороны, отслеживая приложение с помощью JConsole (рис. 2.14), мы не видим, что запуск сборщика мусора для старого поколения не сообщается, хотя мы видим немного колеблющееся старое поколение.
Рисунок 2.14: JConsole показывает, что объем используемой памяти в старом поколении колеблется. В то же время это показывает, что в старом поколении не выполнялась ни одна сборка мусора.
Какой из двух инструментов jStat (рисунок 2.13) или JConsole (рисунок 2.14) верна?
На самом деле, оба верны лишь частично, что вводит в заблуждение. как сообщает jStat, параллельный сборщик мусора был выполнен, но он сделал это асинхронно по отношению к приложению и только для старого поколения, что и является его целью. Это был не полный сборщик мусора! JStat сообщает о сборщиках мусора старого поколения как о полных сборщиках мусора, что неверно. Скорее всего, это наследие дней, предшествовавших параллельным и инкрементным сборщикам мусора.
JConsole сообщает об активациях через Java Management Extensions (JMX) и управляемые bean-компоненты (MBeans).Ранее эти MBean-компоненты сообщали только о реальных крупных сборщиках мусора. В случае CMS это происходит только тогда, когда CMS не может выполнять свою работу одновременно с приложением из-за фрагментации памяти или высокой скорости оттока. По этой причине JConsole не показывает никаких активаций.
В недавнем выпуске CMS, Memory MBean был изменен, чтобы сообщать только об активациях самой CMS; Обратной стороной является то, что у нас больше нет возможности отслеживать реальные крупные сборщики мусора.
В IBM WebSphere JVM основные и второстепенные сборщики мусора неразличимы с помощью JConsole.Единственный способ различить их - использовать флаг verbose: gc.
По этим причинам мы ошибочно игнорируем второстепенные сборщики мусора и переоцениваем сборщики мусора старого поколения, считая их основными. На самом деле нам нужно отслеживать приостановки JVM и понимать, кроются ли первопричины в молодом поколении из-за оттока объектов или в старом поколении из-за неправильного определения размера или утечки памяти.
Анализатор ГХ для максимальной пропускной способности
Мы обсудили влияние блокировки на время отклика приложений.Однако скорость транзакций или более важна для пропускной способности или пакетных приложений. Следовательно, мы не заботимся о влиянии времени паузы на конкретную транзакцию, а об использовании ЦП и общей продолжительности приостановки.
Учтите следующее. В приложении с временем отклика нежелательно приостанавливать одну транзакцию на 2 секунды. Однако допустимо, если каждая транзакция приостанавливается на 50 мс. Если это приложение будет работать в течение нескольких часов, общее время приостановки, конечно, будет намного больше, чем 2 секунды и потребует много ресурсов ЦП, но ни одна транзакция не почувствует этого воздействия! Тот факт, что приложение с высокой пропускной способностью заботится только об общей приостановке и использовании ЦП, должен быть отражен при оптимизации конфигурации сборки мусора.
Чтобы максимизировать пропускную способность, сборщик мусора должен быть максимально эффективным, что означает, что сборщик мусора должен выполнять его как можно быстрее. Поскольку все приложение приостанавливается во время сборки мусора, мы можем и должны использовать все существующие процессоры. В то же время мы должны минимизировать общее использование ресурсов в течение более длительного периода времени. Лучшая стратегия здесь - это параллельный полный сборщик мусора (в отличие от инкрементального дизайна CMS).
Трудно измерить точное использование ЦП при сборке мусора, но есть интуитивно понятный ярлык.Мы просто проверяем общее время выполнения GC. Это можно легко отслеживать с помощью любого доступного бесплатного или коммерческого инструмента. Самый простой способ - использовать jstat -gc 1s. Это покажет нам использование и общее выполнение GC на посекундной основе (см. Таблицу 2.2).
** S0 ** ** S1 ** ** E ** ** O ** ** P ** ** YGC ** ** YGCT ** ** FGC ** ** FGCT ** ** GCT **
0,00 21,76 69,60 9,17 75,80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
0,00 21,76 71,24 9,17 75.80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
0,00 21,76 71,25 9,17 75,80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
0,00 21,76 71,26 9,17 75,80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
0,00 21,76 72,90 9,17 75,80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
0,00 21,76 72,92 9,17 75,80 ** 63 ** ** 2,646 ** ** 20 ** ** 9,303 ** ** 11,949 **
68,74 0,00 1,00 9,17 76,29 _ ** 64 ** _ ** 2,719 ** ** 20 ** ** 9,303 ** _ ** 12.022 ** _
68,74 0,00 29,97 9,17 76,42 ** 64 ** ** 2,719 ** ** 20 ** ** 9,303 ** ** 12,022 **
68,74 0,00 31,94 9,17 76,43 ** 64 ** ** 2,719 ** ** 20 ** ** 9,303 ** ** 12,022 **
68,74 0,00 33,42 9,17 76,43 ** 64 ** ** 2,719 ** ** 20 ** ** 9,303 ** ** 12,022 **
Таблица 2.2: Вместо оптимизации времени отклика нам нужно только посмотреть на общее время сборки мусора.
Используя графические инструменты, время GC можно просматривать в виде диаграмм и, таким образом, легче коррелировать с другими показателями, такими как пропускная способность.На рис. 2.15 мы видим, что хотя сборщик мусора выполняется и довольно часто приостанавливается во всех трех приложениях, он потребляет больше всего времени в GoSpaceBackendit, что, похоже, не влияет на пропускную способность.
Рисунок 2.15. На диаграмме показана продолжительность приостановки сборки мусора трех разных JVM в контексте пропускной способности конкретной JVM.
Можно с уверенностью предположить, что он также требует гораздо больше ресурсов ЦП, чем другие JVM, и имеет более сильное негативное влияние.С увеличением нагрузки время GC также будет увеличиваться; это нормально! Однако время сборки мусора никогда не должно занимать более 10% от общего времени ЦП, используемого приложением. Если загрузка ЦП увеличивается непропорционально загрузке приложения или если загрузка непропорционально высока, необходимо рассмотреть изменение конфигурации сборщика мусора или улучшение приложения.
Анализ распределения
Размещение объектов в Java относительно недорого по сравнению, например, с C ++.Куча поколений специально предназначена для быстрого и частого выделения временных объектов. Но это не бесплатно! Распределение может быстро стать узким местом, когда задействовано много параллельных потоков. Причины включают фрагментацию памяти, более частые сборщики мусора из-за слишком большого количества выделений и синхронизацию из-за одновременных выделений. Хотя вам следует воздерживаться от реализации пулов объектов исключительно ради выделения памяти, отказ от выделения объекта - лучшая оптимизация.
Ключ в том, чтобы оптимизировать ваши алгоритмы и избежать ненужного выделения памяти или выделения одного и того же объекта несколько раз. Например, мы часто создаем временные объекты в циклах. Часто лучше один раз создать такой объект перед циклом и просто использовать его. Это может показаться тривиальным, но в зависимости от размера объекта и количества рекурсий цикла это может иметь большое влияние. И хотя это может показаться очевидным, большая часть существующего кода этого не делает. Распределение таких временных «констант» до цикла - хорошее правило, даже если производительность не имеет значения.
Анализ распределения - это метод выделения областей приложения, которые создают наибольшее количество объектов и, таким образом, обеспечивают наибольший потенциал для оптимизации. Существует ряд бесплатных инструментов, таких как JVisualVM], для выполнения этой формы анализа. Вы должны знать, что все эти инструменты имеют огромное влияние на производительность во время выполнения и не могут использоваться при нагрузках производственного типа. Поэтому нам нужно делать это во время контроля качества или конкретных упражнений по оптимизации производительности.
Также важно проанализировать правильные варианты использования, то есть недостаточно проанализировать только модульный тест для конкретного кода.Большинство алгоритмов управляются данными, поэтому наличие входных данных, аналогичных производственным, очень важно для любого вида анализа производительности, но особенно для анализа распределения.
Рассмотрим конкретный пример. Я использовал JMeter для выполнения некоторых нагрузочных тестов одного из моих приложений. Любопытно, что JMeter не может сгенерировать достаточную нагрузку, и я быстро обнаружил, что он страдает от высокого уровня активности сборщика мусора. Результаты указывают на высокий уровень оттока (много GC молодого поколения). Следовательно, я использовал соответствующий инструмент (в моем случае, JVisualVM), чтобы провести некоторый анализ горячих точек распределения.Прежде чем перейти к результатам, я хочу отметить, что важно начать анализ распределения после фазы разминки проблемы. Профилировщик обычно показывает точки доступа независимо от времени или типа транзакции. Мы не хотим искажать эти горячие точки данными из начальной фазы разогрева (кэширование и запуск). Я просто подождал минуту после запуска JMeter, прежде чем активировать анализ распределения. Фактическое время выполнения аналитического приложения следует игнорировать; в большинстве случаев профилировщик приводит к резкому замедлению работы.
Анализ (рисунок 2.16) показывает, какие типы объектов выделяются больше всего и где это распределение происходит. Наибольший интерес представляют области, которые создают много или большие объекты (большие объекты создают множество других объектов в своем конструкторе или имеют много полей). Мы также должны специально проанализировать области кода, которые, как известно, в производственных условиях работают параллельно. Под нагрузкой эти места не только будут выделять больше, но они также увеличат синхронизацию в самом управлении памятью.В моем сценарии я обнаружил, что было выделено огромное количество объектов Method (рис. 2.16). Это особенно подозрительно, потому что объект Method неизменен и может быть легко использован повторно. Просто нет оправдания тому, чтобы иметь так много новых объектов Method.
Рисунок 2.16: JVisualVM сообщает мне, что объекты Method являются наиболее выделяемыми в моем приложении.
Посмотрев на источник распределения (рис. 2.17), я обнаружил, что объект Interpreter создавался каждый раз, когда скрипт выполнялся, а не использовался повторно.Это привело к выделению объектов Method. Простое повторное использование этого объекта Interpreter удалило более 90% этих распределений.
Рисунок 2.17: JVisualVM сообщает, где в моем коде приложения происходит выделение объекта метода.
Чтобы быстро не сбиться с пути, важно проверять каждую оптимизацию на положительный эффект. Транзакцию нужно тестировать до и после оптимизации при полной нагрузке и, естественно, без профилировщика, а результаты сравнивать друг с другом.Это единственный способ проверить оптимизацию.
Некоторые коммерческие инструменты, такие как Dynatrace (см. Рисунки 2.18 и 2.19), также предлагают возможность тестирования распределения на основе транзакции в условиях нагрузки. Это имеет дополнительное преимущество, заключающееся в учете проблем, связанных с данными, и проблем параллелизма, которые иначе трудно воспроизвести при разработке.
Рисунок 2.18: Dynatrace сообщает мне, как часто конкретный объект выделялся в сценарии нагрузочного тестирования.
Рисунок 2.19. Кроме того, Dynatrace может сказать мне, где в моей транзакции был размещен объект.
Анализ памяти - дамп кучи
До сих пор мы рассматривали влияние сборки и выделения мусора на производительность приложения. Я неоднократно заявлял, что высокая загрузка памяти является причиной чрезмерной сборки мусора. Во многих случаях аппаратные ограничения не позволяют просто увеличить размер кучи JVM. В других случаях увеличение размера кучи не решает, а только задерживает проблему, потому что использование просто продолжает расти.Если это так, пора более внимательно проанализировать использование памяти, просмотрев дамп кучи.
Дамп кучи - это снимок основной памяти. Его можно создать с помощью функций JVM или с помощью специальных инструментов, использующих JVM TI. К сожалению, дампы кучи, интегрированные в JVM, не стандартизированы. В Oracle HotSpot JVM можно создавать и анализировать дампы кучи с помощью jmap, jhat и VisualVM. JRockit включает в себя JRockit Mission Control, который предлагает ряд функций помимо самого анализа памяти.
Дамп кучи сам по себе содержит огромное количество информации, но именно это богатство затрудняет анализ - вы легко утонете в количестве данных. При анализе дампа кучи важны две вещи: хороший инструмент и правильная техника. В целом возможны следующие анализы:
- Выявление утечек памяти
- Идентификация пожирателя памяти
Выявление утечек памяти
Каждый объект, который больше не нужен, но на который ссылается приложение, может рассматриваться как утечка памяти.На практике мы заботимся только об утечках памяти, которые растут или занимают много памяти. Типичная утечка памяти - это утечка, при которой объект определенного типа создается многократно, но не собирается сборщиком мусора (например, потому что он хранится в растущем списке или как часть дерева объектов). Чтобы идентифицировать этот тип объекта, необходимо несколько дампов кучи, которые можно сравнить с помощью дампов тенденций.
Дамп с отслеживанием тенденций дает только статистику количества экземпляров на уровне класса и поэтому может быть предоставлен JVM очень быстро.Сравнение показывает, какие типы объектов имеют тенденцию к росту. Каждое приложение Java имеет большое количество объектов String, char [] и других стандартных объектов Java. Фактически, char [] и String обычно имеют наибольшее количество экземпляров, но их анализ ни к чему не приведет. Даже если бы у нас происходила утечка объектов String, скорее всего, это было бы потому, что на них ссылается объект приложения, который представляет собой основную причину утечки. Поэтому концентрация на классах нашего собственного приложения даст более быстрые результаты.
На рис. 2.20 показано несколько дампов трендов, которые я взял из примера приложения, которое, как я подозревал, имеет утечку памяти. Я проигнорировал все стандартные объекты Java и отфильтровал их по пакету моего приложения. Я сразу нашел тип объекта, количество экземпляров которого постоянно увеличивалось. Поскольку рассматриваемый объект был также известен моему разработчику, мы сразу поняли, что произошла утечка памяти.
Рис. 2.20. Дамп тенденций показывает, какие объекты со временем увеличиваются в количестве.
Выявление пожирателей памяти
Есть несколько случаев, когда вы хотите провести подробный анализ памяти.
- Анализ тенденций не привел вас к утечке памяти
- Ваше приложение использует слишком много памяти, но не имеет явной утечки памяти, и вам необходимо оптимизировать
- Вы не можете выполнить анализ тенденций, потому что память растет слишком быстро, а ваша JVM дает сбой
Во всех трех случаях основной причиной, скорее всего, является один или несколько объектов, которые находятся в корне большего дерева объектов.Эти объекты предотвращают сборку мусора для множества других объектов в дереве. В случае ошибки нехватки памяти вполне вероятно, что горстка объектов препятствует освобождению большого количества объектов, тем самым вызывая ошибку нехватки памяти. Цель анализа дампа кучи - найти эту горстку корневых объектов.
Размер кучи часто является большой проблемой для анализа памяти. Для создания дампа кучи требуется сама память. Если размер кучи находится на пределе того, что доступно или возможно (32-разрядные JVM не могут выделять более 3 файлов.5 ГБ), JVM может не сгенерировать его. Кроме того, дамп кучи приостановит работу JVM. В зависимости от размера это может занять несколько минут или больше. Не стоит этого делать под нагрузкой.
Когда у нас есть дамп кучи, нам нужно его проанализировать. Найти большую коллекцию относительно легко. Однако поиск вручную одного объекта, который предотвращает сборку мусора для всего дерева объектов (или сети), быстро становится пресловутой иглой в стоге сена.
К счастью, такие решения, как Dynatrace, могут автоматически идентифицировать эти объекты.Для этого они используют алгоритм доминирования, основанный на теории графов. Этот алгоритм может вычислять корень дерева объектов. Помимо вычисления корней дерева объектов, инструмент анализа памяти вычисляет, сколько памяти занимает конкретное дерево. Таким образом, он может вычислить, какие объекты предотвращают освобождение большого объема памяти - другими словами, какой объект доминирует в памяти (см. Рис. 2.21).
Рисунок 2.21: Это схематическое представление дерева объектов.В этом представлении объект 1 является корнем дерева и, таким образом, доминирует над всеми остальными.
Давайте рассмотрим это на примере на Рисунке 2.21. Объект 1 является корнем отображаемого дерева объектов. Если объект 1 больше не ссылается на объект 3, все объекты, которые будут поддерживаться в состоянии 3, будут собраны в мусор. В этом смысле объект 3 доминирует над объектами 6, 7, 8 и 9. Объекты 4 и 5 не могут быть обработаны сборщиком мусора, поскольку на них по-прежнему ссылается один другой (объект 2). Однако, если объект 1 разыменован, все показанные здесь объекты будут собраны мусором.Следовательно, объект 1 доминирует над всеми ними.
Хороший инструмент может вычислить динамический размер объекта 1, суммируя размеры всех объектов, над которыми он доминирует. Как только это будет сделано, легко показать, какие объекты доминируют в памяти. На рисунке 2.21 объекты 1 и 3 будут самыми большими горячими точками. Практический способ использования этого - просмотр горячей точки памяти, как показано на рисунке 2.22:
.
Рисунок 2.22. Это представление горячей точки показывает, что доминирует массив HashMap $ Entry, занимающий более 80 процентов кучи.
Массив HashMap $ Entry доминирует - на него приходится более 80 процентов кучи. Если бы он собирался сборщиком мусора, 80% нашей кучи было бы пустым. Отсюда относительно просто использовать дамп кучи, чтобы выяснить, почему. Затем мы можем легко понять и показать, почему сам этот объект не был обработан сборщиком мусора. Еще более полезно знать, над какими объектами доминируют и которые составляют эту горячую точку. В Dynatrace мы называем это набором Keep Alive Set (см. Рис. 2.23).
Рисунок 2.23: Набор keep alive показывает объекты, над которыми доминирует или поддерживает массив HashMap $ Entry.
С помощью такого анализа можно в течение нескольких минут определить, есть ли большая утечка памяти или кэш использует слишком много памяти.
.