SmokiMo 892 Опубликовано 14 августа, 2017 В данной заметке я хочу показать каким образом можно определять и устранять утечки памяти в Java на примере из моей повседневной работы. Мы не будем здесь рассматривать возможные причины появления утечек, об этом будет отдельная статья, так как тема достаточно обширная. Стоит заметить, что речь пойдет о диагностике именно Heap Memory, об утечках в других областях памяти будет отдельная статья. Инструменты Для успешной диагностики нам понадобятся два инструмента: Java Mission Control (jmc) и Eclipse Memory Analyzer. Вобщем-то можно обойтись только Memory Analyzer, но с JMC картина будет более полной. JMC входит в состав JDK (начиная с 1.7) Memory Analyzer может быть загружен отсюда: MAT Анализ использования памяти Прежде всего, нужно запустить приложение со следующими флагами JVM: -XX:+UnlockCommercialFeatures -XX:+FlightRecorder Не используйте эти опции на production системе без приобретения специальной лицензии Oracle! Эти опции позволят запустить Flight Recorder – утилита, которая поможет собрать информацию об использовании памяти (и много другой важной информации) во время выполнения программы. Я не буду описывать здесь как запустить Flight Recorder, эта информация легко гуглится. В моем случае было достаточно запустить FR на 10-11 минут. Рассмотрим следующий рисунок, на котором показана классическая «пила» памяти, а так же важный сигнал, что что-то не так с использованием памяти: Можно увидеть, что после каждого цикла очистки памяти, heap все больше заполняется, я выделил это желтым треугольником. «Пила» все время как бы ползет вверх. Это значит, что какие-то объекты не достижимы для очистки и накапливаются в old space, что со временем приведет к переполнению этой области памяти. Выявление утечки Следующим шагом нужно выявить, что именно не доступно для очистки и в этом нам поможет Memory Analyzer. Прежде всего, нужно загрузить в программу heap dump работающего приложения с предполагаемой утечкой памяти. Это можно сделать с помощью «File → Acquire Heap Dump». После загрузки в диалоге «Getting Started Wizard» выбрать «Leak Suspects Report» после этого откроется краткий обзор возможных утечек памяти: Если вернуться на вкладку «Overview» и выбрать «Dominator Tree», то можно увидеть более подробную картину: Дерево показывает структуру «тяжелого» объекта, а так же размер его полей (по типу). Можно видеть, что одно из полей объекта MasterTenant занимает более 45% памяти. Устранение утечки Имея результат анализа из предыдущего пункта, следующим шагом идет устранение накапливания объектом памяти. Тут все сильно зависит от конкретного кода. Общая рекоменация – нужно найти и проанализировать все места, где происходит инициализация или изменение соответствующего поля или полей, чтобы понять механизм накапливания памяти. В моем случае в коллекцию постоянно добавлялись записи из множества (около 150) потоков при определенных условиях. После находжения и устранения утечки, не лишним будет пройти все шаги снова, проанализировать память и отчет Memory Analyzer, чтобы убедиться что фикс помог. Ссылка на оригинал 6 Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
ncsSpawN 45 Опубликовано 14 августа, 2017 Пользуюсь VisualVM куда лучше и удобней, а так сути не меняет. Хотел бы в добавок подкрепить ещё статейку. http://learn.javajoy.net/java-memory-leaks 1 Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
Urban 78 Опубликовано 14 августа, 2017 Полезная статья)) 1 Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
SmokiMo 892 Опубликовано 14 августа, 2017 Пользуюсь VisualVM куда лучше и удобней, а так сути не меняет. Хотел бы в добавок подкрепить ещё статейку. http://learn.javajoy.net/java-memory-leaks Да, с этой статьей тоже знаком, хотел и ее добавить 1 Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
Mangol 101 Опубликовано 14 августа, 2017 (изменено) Это значит, что какие-то объекты не достижимы для очистки и накапливаются в old space, что со временем приведет к переполнению этой области памяти. Я не согласен с этим, если объект попал в old space, это не значит что его нельзя очистить, это значит лишь то, что объект пережил чистки, и не нуждается пока что в чистке. Но как только дело подойдет к пределу old gen, и его будет мало, он будет запускать полную очистку, шерстя по old space. Более точная инфа вроде вот тут. http://netflix.github.io/spectator/en/latest/ext/jvm-gc-causes/ Обычно сразу чистятся локальные ссылки. YourKit либо на крайний случай можно использовать VisualVM, но на сколько я помню, он не сохраняет показания которые пришла. В отличии от YourKit Изменено 14 августа, 2017 пользователем Mangol Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
PointerRage 132 Опубликовано 14 августа, 2017 как только дело подойдет к пределу old gen, и его будет мало, он будет запускать полную очистку Не совсем: FullGC не всегда вызывается, когда места в oldgen не хватает (кстати говоря, он же и переносит из элдена в олд объекты). Зависит чуть более чем полностью от подключенного GC и выставленных настроек поколений/сборщика/etc. У CMS, вообще, еще больше типов поколений объектов хипа (а также в его замене, которая придет в Java 9). G1 - разбивает весь хип на чанки и чистятся чанки, а не сразу все старое поколение. Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты
Mangol 101 Опубликовано 14 августа, 2017 (изменено) Не совсем: FullGC не всегда вызывается, когда места в oldgen не хватает (кстати говоря, он же и переносит из элдена в олд объекты). Зависит чуть более чем полностью от подключенного GC и выставленных настроек поколений/сборщика/etc. У CMS, вообще, еще больше типов поколений объектов хипа (а также в его замене, которая придет в Java 9). G1 - разбивает весь хип на чанки и чистятся чанки, а не сразу все старое поколение. В ссылке указано это, я лишь подчеркнул в каких случаях он может вызывать его. Ну все верно. G1 создает ячейки, по дефолту их около 2 тысяч на сколько я помню. Ну так оракл гласит по доке. Так же он отдельно большие части ложит отдельно. Ну, и самое приятно что он более точно прогнозирует STW в отличии от предыдущих поколений, и старается вложится в это время. Да и вообще, он может вызывать фулл, не только когда в old gen не хватает памяти. В G1 всё разбито на множество ячеек, что довольно умно в отличии от предыдущих поколений GC.(Parralel, CMS),ну самое первое поколение думаю скоро выпилят. Пока по дефолту java 8 использует Parralel. Изменено 14 августа, 2017 пользователем Mangol Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты