Метод обнаружения утечки памяти в больших дампах кучи Java

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

О нашей ситуации:

  1. Дампы кучи больше 1 ГБ
  2. У нас есть дампы кучи из 5 раз.
  3. У нас нет никакого тестового случая, чтобы спровоцировать это. Это происходит только в (массивной) тестовой среде системы после как минимум недели использования.
  4. Система построена на устаревшем фреймворке собственной разработки с таким количеством конструктивных недостатков, что их невозможно сосчитать.
  5. Никто не понимает структуру глубоко. Он был передан одному парню в Индии, который едва успевает отвечать на электронные письма.
  6. Мы сделали дампы кучи моментальных снимков с течением времени и пришли к выводу, что ни один компонент не увеличивается с течением времени. Это все, что растет медленно.
  7. Вышеизложенное указывает нам на то, что это собственная ORM-система фреймворка, которая увеличивает свое использование без ограничений. (Эта система сопоставляет объекты с файлами?! Так что это не совсем ORM)

Вопрос. Какая методология помогла вам добиться успеха в поиске утечек в корпоративном приложении?


person Rickard von Essen    schedule 24.03.2010    source источник
comment
@LB У нас есть 64 ГБ, но бюджет для этого приложения составляет всего 2 ГБ, и мы не можем разумно увеличить больше, чем на пару ГБ, не начав каннибализировать другие подсистемы.   -  person Rickard von Essen    schedule 25.03.2010
comment
Дампы кучи специфичны для JVM, поэтому вам необходимо использовать инструмент, соответствующий рассматриваемой JVM. Это?   -  person Thorbjørn Ravn Andersen    schedule 25.03.2010
comment
@ Thorbjørn Равн Андерсен Мы используем Sun Java SDK 1.6   -  person Rickard von Essen    schedule 25.03.2010
comment
Хорошо, не думали ли вы подключиться к длительному процессу с помощью visualvm, чтобы увидеть, как все развивается с течением времени? Есть автономная версия и та, что в JDK.   -  person Thorbjørn Ravn Andersen    schedule 25.03.2010
comment
@ Thorbjørn Ravn Andersen Я кратко обдумал это, но отложил, так как ожидаю, что это будет очень медленно. Но, возможно, пришло время попробовать это.   -  person Rickard von Essen    schedule 25.03.2010
comment
См. также stackoverflow.com/q/7254017/32453.   -  person rogerdpack    schedule 25.10.2016


Ответы (8)


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

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

Я провел последние пару недель, делая именно это, и я использовал итеративный процесс.

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

Скорее, я почти полностью полагался на гистограммы jmap. .

Я полагаю, вы знакомы с ними, но для тех, кто нет:

jmap -histo:live <pid> > dump.out

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

Я сбрасывал кучу регулярно, каждые 5 минут, 24 часа в сутки. Возможно, это слишком подробно для вас, но суть та же.

Я провел несколько различных анализов этих данных.

Я написал скрипт, который берет две гистограммы и выводит разницу между ними. Таким образом, если java.lang.String было равно 10 в первом дампе и 15 во втором, мой сценарий выдал бы «5 java.lang.String», сообщая мне, что он увеличился на 5. Если он уменьшился, число было бы отрицательным.

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

Тем не менее, некоторые классы сохранились, а другие были GC. Эти классы могут легко повышаться и понижаться в целом, но все равно будут протекать. Таким образом, они могли выпасть из категории «всегда повышающихся» классов.

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

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

Теперь все, что он может сделать, это определить классы с потенциальной утечкой.

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

Например, вы можете обнаружить, что у вас много классов Map.Entry или какой-то другой системный класс.

Если вы просто не кэшируете String, дело в том, что эти системные классы, хотя, возможно, и «нарушители», не являются «проблемой». Если вы кэшируете какой-то класс приложения, ЭТОТ класс является лучшим индикатором того, в чем заключается ваша проблема. Если вы не кэшируете com.app.youurbean, у вас не будет связанной с ним Map.Entry.

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

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

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

Профилировщик может помочь вам отследить владельцев этого «теперь просочившегося» класса.

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

person Will Hartung    schedule 24.03.2010

Взгляните на анализатор памяти Eclipse. Это отличный инструмент (и автономный, не требует установки самого Eclipse), который 1) может очень быстро открывать очень большие кучи и 2) имеет несколько довольно хороших инструментов автоматического обнаружения. Последнее не идеально, но EMA предоставляет множество действительно хороших способов навигации и запроса объектов в дампе, чтобы найти любые возможные утечки.

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

person matt b    schedule 24.03.2010
comment
Я использовал это, чтобы успешно проанализировать дамп кучи ~ 180 мегабайт только вчера, работает как шарм. - person Esko; 25.03.2010
comment
Eclipse MAT великолепен, особенно это детектор утечки памяти. - person Pascal Thivent; 25.03.2010
comment
Да, это то, что мы в основном используем. Он прекрасно работает с дампом кучи размером не менее 1,5 ГБ в 64-битной Linux (конечно, 32-битная Win быстро выходит из строя). Единственным недостатком является то, что я не получил очень полезной помощи от автоматического анализа в нем. - person Rickard von Essen; 25.03.2010
comment
Кажется, он использует примерно столько же оперативной памяти, сколько файл кучи [что, к сожалению, слишком много в моем случае]. Лучше Джата... - person rogerdpack; 31.03.2015

Этот ответ расширяет ответ @Will-Hartung's. Я применил тот же процесс для диагностики одной из моих утечек памяти и подумал, что если поделиться подробностями, это сэкономит время другим людям.

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

    ^
    |
s   |  Legend:
i   |  *  - data point
z   |  -- - trend
e   |
(   |
b   |                 *
y   |                     --
t   |                  --
e   |             * --    *
s   |           --
)   |       *--      *
    |     --    *
    |  -- *
   --------------------------------------->
                      time

Преобразуйте свои дампы кучи (нужно несколько) в формат, который удобен для использования postgres из формата дампа кучи:

 num     #instances         #bytes  class name 
----------------------------------------------
   1:       4632416      392305928  [C
   2:       6509258      208296256  java.util.HashMap$Node
   3:       4615599      110774376  java.lang.String
   5:         16856       68812488  [B
   6:        278914       67329632  [Ljava.util.HashMap$Node;
   7:       1297968       62302464  
...

В файл csv с датой и временем каждого дампа кучи:

2016.09.20 17:33:40,[C,4632416,392305928
2016.09.20 17:33:40,java.util.HashMap$Node,6509258,208296256
2016.09.20 17:33:40,java.lang.String,4615599,110774376
2016.09.20 17:33:40,[B,16856,68812488
...

Используя этот скрипт:

# Example invocation: convert.heap.hist.to.csv.pl -f heap.2016.09.20.17.33.40.txt -dt "2016.09.20 17:33:40"  >> heap.csv 

 my $file;
 my $dt;
 GetOptions (
     "f=s" => \$file,
     "dt=s" => \$dt
 ) or usage("Error in command line arguments");
 open my $fh, '<', $file or die $!;

my $last=0;
my $lastRotation=0;
 while(not eof($fh)) {
     my $line = <$fh>;
     $line =~ s/\R//g; #remove newlines
     #    1:       4442084      369475664  [C
     my ($instances,$size,$class) = ($line =~ /^\s*\d+:\s+(\d+)\s+(\d+)\s+([\$\[\w\.]+)\s*$/) ;
     if($instances) {
         print "$dt,$class,$instances,$size\n";
     }
 }

 close($fh);

Создайте таблицу, чтобы поместить данные в

CREATE TABLE heap_histogram (
    histwhen timestamp without time zone NOT NULL,
    class character varying NOT NULL,
    instances integer NOT NULL,
    bytes integer NOT NULL
);

Скопируйте данные в новую таблицу

\COPY heap_histogram FROM 'heap.csv'  WITH DELIMITER ',' CSV ;

Запустите запрос slop для запроса размера (количество байтов):

SELECT class, REGR_SLOPE(bytes,extract(epoch from histwhen)) as slope
    FROM public.heap_histogram
    GROUP BY class
    HAVING REGR_SLOPE(bytes,extract(epoch from histwhen)) > 0
    ORDER BY slope DESC
    ;

Интерпретируйте результаты:

         class             |        slope         
---------------------------+----------------------
 java.util.ArrayList       |     71.7993806279174
 java.util.HashMap         |     49.0324576155785
 java.lang.String          |     31.7770770326123
 joe.schmoe.BusinessObject |     23.2036817108056
 java.lang.ThreadLocal     |     20.9013528767851

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

Моя одна из строк кода, создающая этот joe.schmoe.BusinessObject, была ответственна за утечку памяти. Он создавал объект, добавляя его в массив, не проверяя, существует ли он уже. Другие объекты также были созданы вместе с BusinessObject рядом с утечкой кода.

person joseph    schedule 28.10.2016
comment
Эй, отличный ответ. какой наклон (байты/время) вы считаете нормальным наклоном. Например, если для какого-то объекта у вас есть наклон 3,45, будете ли вы рассматривать это как утечку памяти? - person Kumar Shubham; 25.09.2019

Можешь ли ты ускорить время? т. е. можете ли вы написать фиктивный тестовый клиент, который заставит его выполнять звонки/запросы и т. д. в течение нескольких недель за несколько минут или часов? Это ваш самый большой друг, и если у вас его нет — напишите.

Некоторое время назад мы использовали Netbeans для анализа дампов кучи. Это может быть немного медленно, но это было эффективно. Eclipse только что разбился, и 32-битные инструменты Windows тоже.

Если у вас есть доступ к 64-битной системе или системе Linux с 3 ГБ или более, вам будет проще анализировать дампы кучи.

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

Когда все пошло не так? Поговорите с людьми и попытайтесь получить немного истории. Вы можете услышать, как кто-то скажет: «Да, именно после того, как они исправили XYZ в патче 6.43, у нас стали происходить странные вещи».

person Fortyrunner    schedule 24.03.2010
comment
Мы думали о том, что это должно быть хорошей идеей, но в нашем случае это неосуществимо в дыре. Мы можем выполнять только некоторые тестовые случаи чаще. Системный тест проводится каждые 6 месяцев или около того, и в последний раз они решили сделать его более интенсивным. После этого мы нашли проблему. Мы попытались понизить фреймворк и приложение до версии, которая ранее прошла тест. Все три теста не пройдены, это говорит нам о том, что неисправность эфира в другом компоненте в системе или уже давно в нашем. Другой компонент маловероятен. - person Rickard von Essen; 25.03.2010

Мне удалось успешно использовать IBM Heap Analyzer. Он предлагает несколько представлений кучи, в том числе наибольшее уменьшение размера объекта, наиболее часто встречающиеся объекты и объекты, отсортированные по размеру.

person Drew Johnson    schedule 24.03.2010

Существуют отличные инструменты, такие как Eclipse MAT и Heap Hero, для анализа дампов кучи. Однако вам необходимо предоставить этим инструментам дампы кучи, захваченные в правильном формате и в правильный момент времени.

В этой статье представлены несколько вариантов захвата дампов кучи. Однако, на мой взгляд, первые 3 являются эффективными вариантами, а остальные — хорошими вариантами, о которых следует знать. 1. jmap 2. HeapDumpOnOutOfMemoryError 3. jcmd 4. JVisualVM 5. JMX 6. Программный подход 7. Административная консоль IBM

7 вариантов захвата Java Дампы кучи

person Jim T    schedule 24.06.2019

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

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

person Brian Agnew    schedule 24.03.2010
comment
Решение для окон! (Наш ИТ-отдел использует это для наших серверов Windows). Мы не запускаем систему сами, она продается компаниям, которые не могут принимать перезапуски (запланированные или незапланированные). Один только признак нестабильности вызовет угрозы штрафов. - person Rickard von Essen; 25.03.2010
comment
Мне это не нравится, но в некоторых сценариях это прагматично. Однако я отмечаю ваше замечание о продаже компаниям. - person Brian Agnew; 25.03.2010
comment
Я согласен, что в некоторых случаях это может быть (временным) решением. - person Rickard von Essen; 25.03.2010

Я использовал jhat, это немного жестко, но это зависит от того, какой у вас был фреймворк.

person LB40    schedule 24.03.2010
comment
Нам не удалось загрузить такую ​​большую кучу дампов в jhat, и это, похоже, обычная проблема. Кроме того, насколько я помню, когда я использовал его два года назад, он был немного медленным на больших наборах данных. - person Rickard von Essen; 25.03.2010
comment
как еще один анекдот, у меня также были проблемы с загрузкой jhat и больших (1G) куч - person matt b; 25.03.2010
comment
что за проблема? проблема с кучей пространства на jvm, на котором работает jhat? - person LB40; 25.03.2010