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

142

Как вы находите утечку памяти в Java (используя, например, JHat)? Я попытался загрузить дамп кучи в JHat, чтобы получить базовый взгляд. Тем не менее, я не понимаю, как я могу найти корневую ссылку ( ref ) или как она там называется. По сути, я могу сказать, что существует несколько сотен мегабайт записей хеш-таблиц ([java.util.HashMap $ Entry или что-то в этом роде), но карты используются повсеместно ... Есть ли способ поиска больших карт или, может быть, найти общие корни деревьев больших объектов?

[Править] Хорошо, я до сих пор читал ответы, но давайте просто скажем, что я дешевый ублюдок (то есть меня больше интересует изучение того, как использовать JHat, чем платить за JProfiler). Кроме того, JHat всегда доступен, поскольку он является частью JDK. Если, конечно, с JHat нет другого пути, кроме грубой силы, но я не могу поверить в это.

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

jwiklund
источник
Это еще один «голос» за JProfiler. Он хорошо работает для анализа кучи, имеет приличный интерфейс и работает довольно хорошо. Как говорит McKenzieG1, 500 долларов дешевле, чем количество времени, которое вы могли бы потратить на поиск источника этих утечек. Что касается цены на инструменты, это не плохо.
Иоев
У Oracle есть соответствующая страница здесь: docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/…
Лорел

Ответы:

126

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

  1. Запустите приложение и подождите, пока оно не перейдет в «стабильное» состояние, когда вся инициализация завершена и приложение находится в режиме ожидания.
  2. Несколько раз запустите операцию, подозреваемую на утечку памяти, чтобы разрешить любую кеш-инициализацию, связанную с БД.
  3. Запустите GC и сделайте снимок памяти.
  4. Запустите операцию снова. В зависимости от сложности операции и размеров обрабатываемых данных может потребоваться запуск операции от нескольких до нескольких раз.
  5. Запустите GC и сделайте снимок памяти.
  6. Запустите diff для 2 снимков и проанализируйте его.

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

Для веб-приложений, обрабатывающих запросы в нескольких потоках, анализ становится более сложным, но, тем не менее, общий подход по-прежнему применяется.

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

Дима Маленко
источник
7
Большинство (если не все) профилировщики Java предоставляют вам возможность вызывать GC одним нажатием кнопки. Или вы можете вызвать System.gc () из соответствующего места в вашем коде.
Дима Маленко
3
Даже если мы вызываем System.gc (), JVM может игнорировать вызов. AFAIK, это специфично для JVM. +1 к ответу.
Аникет Тхакур
4
Что такое «снимок памяти»? Есть ли что-то, что скажет мне номер каждого типа объекта, который выполняется моим кодом?
гномед
2
Как мне перейти от «начать с наибольшей положительной разницы по типам объектов» к «найти, что заставляет эти дополнительные объекты оставаться в памяти»? Я вижу очень общие вещи, такие как int [], Object [], String и т. Д. Как мне найти, откуда они берутся?
Vituel
48

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

Поскольку люди предлагают несколько инструментов (я пробовал только Visual WM, так как я получил это в пробной версии JDK и JProbe), хотя я должен предложить бесплатный инструмент с открытым исходным кодом, построенный на платформе Eclipse, анализатор памяти (иногда упоминаемый как память SAP анализатор) доступно на http://www.eclipse.org/mat/ .

Что действительно круто в этом инструменте, так это то, что он впервые проиндексировал дамп кучи, когда я впервые открыл его, что позволило ему отображать данные, такие как сохраненная куча, без ожидания 5 минут для каждого объекта (почти все операции были на много быстрее, чем другие инструменты, которые я пробовал) ,

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

jwiklund
источник
1
Стоит отметить: по-видимому, в Java 5 и выше, HeapDumpOnCtrlBreakпараметр VM недоступен . Решение, которое я нашел (до сих пор продолжаю искать), состоит в том, чтобы использовать JMap для выгрузки .hprofфайла, который я затем помещаю в Eclipse и использую MAT для проверки.
Бен
1
Что касается получения дампа кучи, большинство профилировщиков (включая JVisualVM) включают опцию для выгрузки кучи и потоков в файл.
bbaja42
13

Инструмент - большая помощь.

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

В этом случае это поможет вам разобраться в файле дампа hprof.

Ищите САЙТ НАЧАТЬ. Это показывает, какие объекты используют больше всего памяти. Но объекты не объединяются только по типу: каждая запись также содержит идентификатор «trace». Затем вы можете найти этот «TRACE nnnn», чтобы увидеть несколько верхних фреймов стека, в котором был размещен объект. Часто, когда я вижу, где расположен объект, я нахожу ошибку и все готово. Также обратите внимание, что вы можете контролировать, сколько кадров записывается в стеке с помощью параметров -Xrunhprof.

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

Чтобы создать цепочку, посмотрите в разделе HEAP DUMP записи с ошибочным идентификатором трассировки. Это приведет вас к записи OBJ или ARR, которая показывает уникальный идентификатор объекта в шестнадцатеричном формате. Поиск всех вхождений этого идентификатора, чтобы найти, кто имеет сильную ссылку на объект. Следуйте каждому из этих путей в обратном направлении, пока они не разветвляются, пока не выясните, где находится утечка. Видишь, почему инструмент так удобен?

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

Эриксон
источник
«Дамп кучи настолько велик, что он ломает инструмент», - последнее время я проверял jhatи, по- MATвидимому, пытался загрузить весь дамп кучи в память и, как правило, вылетал с OutOfMemoryErrorбольшими дампами (т. Е. Из приложений, которые больше всего нуждались в анализе кучи! ). Профилировщик NetBeans, похоже, использует другой алгоритм для индексации ссылок, который может работать медленно на больших дампах, но, по крайней мере, не использует неограниченную память в инструменте и аварийно завершает работу.
Джесси Глик
10

В большинстве случаев в корпоративных приложениях указанная куча Java больше идеального размера - от 12 до 16 ГБ. Мне было трудно заставить профилировщик NetBeans работать непосредственно с этими большими Java-приложениями.

Но обычно это не нужно. Вы можете использовать утилиту jmap, которая поставляется вместе с jdk, чтобы получить «живой» дамп кучи, то есть jmap выгрузит кучу после запуска GC. Сделайте некоторую операцию с приложением, дождитесь завершения операции, затем сделайте еще один «живой» дамп кучи. Используйте такие инструменты, как Eclipse MAT, чтобы загружать heapdumps, сортировать по гистограмме, видеть, какие объекты увеличились или какие являются самыми высокими. Это даст подсказку.

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

Есть только одна проблема с этим подходом; Огромные дампы кучи, даже с опцией live, могут быть слишком большими для переноса на этап разработки, и для их открытия может потребоваться машина с достаточным объемом памяти / ОЗУ.

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

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

Вместо двух дампов кучи возьмите две гистограммы классов, как описано выше; Затем сравните гистограммы классов и посмотрите, какие классы увеличиваются. Посмотрите, можете ли вы связать классы Java с классами вашего приложения. Это даст довольно хороший намек. Вот скрипт pythons, который может помочь вам сравнить два дампов гистограммы jmap. histogramparser.py

Наконец, такие инструменты, как JConolse и VisualVm, необходимы для наблюдения за увеличением объема памяти и выявления утечки памяти. Наконец, иногда ваша проблема может заключаться не в утечке памяти, а в ее большом использовании памяти. Для этого включите ведение журнала GC, используйте более продвинутый и новый GC сжатия, такой как G1GC; и вы можете использовать инструменты JDK, такие как Jstat, чтобы увидеть поведение GC в прямом эфире

jstat -gccause pid <optional time interval>

Другие ссылки на Google для -jhat, Jmap, Full GC, Humongous распределения, G1GC

Алекс Пуннен
источник
1
добавил пост в блоге с более подробной информацией здесь - alexpunnen.blogspot.in/2015/06/…
Алекс Пуннен
5

Существуют инструменты, которые должны помочь вам найти утечку, такие как JProbe, YourKit, AD4J или JRockit Mission Control. Последнее, что я лично знаю лучше всего. Любой хороший инструмент должен позволить вам перейти к уровню, на котором вы можете легко определить, какие утечки и где распределяются объекты утечки.

Использование HashTables, Hashmaps или аналогичных является одним из немногих способов, которыми вы можете вообще утечь память в Java. Если бы мне пришлось искать утечку вручную, я бы перидически печатал размер моих HashMaps, а оттуда находил тот, где я добавлял элементы, и забывал их удалять.

Tnilsson
источник
4

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

Майк Стоун
источник
1

NetBeans имеет встроенный профилировщик.

wbkang
источник
0

Вам действительно нужно использовать профилировщик памяти, который отслеживает распределение. Взгляните на JProfiler - их отличная функция «обхода кучи» и их интеграция со всеми основными Java IDE. Это не бесплатно, но и не так дорого (499 долл. За одну лицензию) - вы потратите на 500 долл. Времени довольно быстро, пытаясь найти утечку с помощью менее сложных инструментов.

McKenzieG1
источник
0

Это можно узнать, измерив объем использования памяти после многократного вызова сборщика мусора:

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

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

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

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

Амир Фо
источник
0

Оформить заказ этот скриншот о поиске утечек памяти с JProfiler. Это визуальное объяснение @Dima Malenko Ответ.

Примечание: хотя JProfiler не является бесплатной, но пробная версия может справиться с текущей ситуацией.

Вайбхав Джайн
источник
0

Поскольку большинство из нас уже используют Eclipse для написания кода, почему бы не использовать инструмент анализатора памяти (MAT) в Eclipse. Работает отлично.

Затмение MAT представляет собой набор плагинов для IDE Eclipse , который предоставляет инструменты для анализа heap dumpsиз приложения Java и определить memory problemsв приложении.

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

  1. Получение снимка памяти (дамп кучи)
  2. Гистограмма
  3. Сохраненная куча
  4. Дерево Доминатор
  5. Изучение путей к корням GC
  6. Инспектор
  7. Общие анти-паттерны памяти
  8. Язык запросов объектов

введите описание изображения здесь

Срирам Наир
источник