В данной заметке я хочу показать каким образом можно определять и устранять утечки памяти в 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 минут.

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

Запись Fight recorder

Можно увидеть, что после каждого цикла очистки памяти, heap все больше заполняется, я выделил это желтым треугольником. «Пила» все время как бы ползет вверх. Это значит, что какие-то объекты не достижимы для очистки и накапливаются в old space, что со временем приведет к переполнению этой области памяти.

Выявление утечки


Следующим шагом нужно выявить, что именно не доступно для очистки и в этом нам поможет Memory Analyzer. Прежде всего, нужно загрузить в программу heap dump работающего приложения с предполагаемой утечкой памяти. Это можно сделать с помощью «File > Acquire Heap Dump». После загрузки в диалоге «Getting Started Wizard» выбрать «Leak Suspects Report» после этого откроется краткий обзор возможных утечек памяти:

Leak suspects report

Если вернуться на вкладку «Overview» и выбрать «Dominator Tree», то можно увидеть более подробную картину:

Overview

Denominator tree

Дерево показывает структуру «тяжелого» объекта, а так же размер его полей (по типу). Можно видеть, что одно из полей объекта MasterTenant занимает более 45% памяти.

Устранение утечки


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

После находжения и устранения утечки, не лишним будет пройти все шаги снова, проанализировать память и отчет Memory Analyzer, чтобы убедиться что фикс помог.
Поделиться с друзьями
-->

Комментарии (22)


  1. grossws
    16.03.2017 20:14
    +5

    Прежде всего, нужно запустить приложение со следующими флагами JVM:
    -XX:+UnlockCommercialFeatures
    -XX:+FlightRecorder

    Эти опции позволят запустить Flight Recorder – утилита, которая поможет собрать информацию об использовании памяти (и много другой важной информации) во время выполнения программы. Я не буду описывать здесь как запустить Flight Recorder, эта информация легко гуглится. В моем случае было достаточно запустить FR на 10-11 минут.

    К этому всегда полезно добавлять дисклеймер, о том что эти фичи нельзя использовать в production-окружении не имея коммерческой лицензии на Oracle JDK. Для разработки/отладки JMC использовать можно спокойно.


    1. alexrewa
      17.03.2017 18:52

      Спасибо за замечание, добавил предупрежение


  1. vektory79
    17.03.2017 11:08
    +1

    Ещё полезная ссылка: http://blogs.atlassian.com/2013/03/so-you-want-your-jvms-heap/


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


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


    1. alexrewa
      17.03.2017 11:23

      Спасибо, изучу материал по ссылке


    1. johndow
      17.03.2017 16:45

      Затверждаю. Очень полезная метода.
      Только так удалось снять дамп с живого прода.


    1. vektory79
      20.03.2017 16:25

      К стати, если делать дамп таким макаром, то там ещё и информация о потоках сохраняется, Это позволяет сразу переходить к списку потоков (большая жёлтая шестерёнка на скриншоте MAT) и смотреть какой поток сколько памяти удерживает и почему. Сокращает время поиска причины утечки памяти буквально до нескольких минут.


      Буквально вот только что очередной дамп расковырял. Основное время ушло на перекачку дампа к себе на комп и его индексирование. Далее буквально за пару минут виноватого нашёл.


  1. Marahal
    17.03.2017 11:19

    При использовании Commercial Features надо не забыть купить лицензию на Java SE Advanced Desktop (40$) или Java SE Advanced (5000$) или Java SE Suite (15000$).


    1. grossws
      17.03.2017 13:24

      Как я говорил выше, для разработки (не в production) можно использовать JRF не имея коммерческой лицензии:


      The Java Flight Recorder (JFR) is a commercial feature. You can use it for free on developer desktops/laptops, and for evaluation purposes in test, development, and production environments. However, to enable JFR on a production server, you require a commercial license. Using JMC UI for other purposes on the JDK does not require a commercial license.

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


  1. binakot
    17.03.2017 11:20

    Не думаю, что в этом контексте вообще нужно упоминать про коммерческие фичи. Все это видно и через JMX в том же VisualVM.


    1. grossws
      17.03.2017 13:26
      +1

      Как только вы набрали -XX:+UnlockCommercialFeatures, нужно говорить про коммерческие фичи, если вы сделали это на проде (если говорить в контексте JMC/JFR). Может, конечно, факт неиспользования включенной фичи уменьшит штраф в судебном решении ,)


  1. alxt
    17.03.2017 11:20
    +3

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

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


    1. alexrewa
      17.03.2017 12:01

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


  1. FireWolf2007
    17.03.2017 11:20

    А принципиальные отличия/преимущества от VisualVM? Какое-то сравнение с VisualVM можете сделать?


    1. alexrewa
      17.03.2017 11:23

      Например такое принципиальное отличие как возможность сделать запись работы VM и потом подробно изучать его. В Visual VM можно смотреть только в реалтайме.


    1. alxt
      17.03.2017 11:43

      Если речь про JMC — то ещё возможность семплирования (но работает только в safe-points, а это недостоверно).
      Если про EMT — то GC-root как-то лучше работает (в visualVM оно то ли не работает, то ли неработоспособно). Но памяти этот зверь кушает- очень хорошо.

      PS: apangin вроде как делал своё семплирование, которое работает вне safe-point — но всё ссылку на утилитку не могу найти. Подскажите, кто помнит, пожалуйста :)


      1. apangin
        17.03.2017 17:54

        Да, только это для профилирования CPU, а не Memory.
        https://github.com/apangin/async-profiler

        Кстати говоря, на предстоящем JPoint будем с коллегой подробно рассказывать как раз про методики семлирования, и к этому событию приурочу кое-какие интересные обновления в async-profiler… Следите за новостями :)


  1. AlexeyPi
    17.03.2017 18:24

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


  1. Irina888
    17.03.2017 18:48
    -1

    Спасибо за тему!!!!!


  1. Infthi
    19.03.2017 13:12
    +2

    > «Пила» все время как бы ползет вверх. Это значит, что какие-то объекты не достижимы для очистки и накапливаются в old space, что со временем приведет к переполнению этой области памяти.

    Плохая иллюстрация, мы просто не увидели full GC — который собрал бы мусор, случайно попадающий в old space. Соответственно на хорошей иллюстрации должно быть более одного цикла такого full gc для понимания его динамики.

    И, кстати, зачем надо было частично(!) неодинаково(!) замазывать одинаковые(!) имена пакетов на скриншоте? они же легко мержатся и начинают читаться: image


    1. rauch
      26.03.2017 21:20

      мы просто не увидели full GC — который собрал бы мусор, случайно попадающий в old space


      Случайно? В янг спейс не влезло — это сегодня называется «случайно»? При условии, что в дефолтных настройках хип делится в соотношении 1 к 2 (1/3 для янг спейса, 2/3 для олд)


  1. TOBBOT
    20.03.2017 11:07
    -1

    Кстати, в NetBeans есть встроенный профайлер, который позволит сделать (даже в динамике) тоже самое не покидая родную IDE.


    1. vlanko
      20.03.2017 13:47

      Да, это вроде VisualVM.
      Только жаль, что в Java6 не работает.