Метод обнаружения утечки памяти в больших дампах Java-кучи - PullRequest
27 голосов
/ 24 марта 2010

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

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

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

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

Ответы [ 8 ]

56 голосов
/ 25 марта 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

13 голосов
/ 24 марта 2010

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

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

5 голосов
/ 28 октября 2016

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

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

    ^
    |
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 рядом с утечкой кода.

3 голосов
/ 25 марта 2010

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

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

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

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

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

2 голосов
/ 25 марта 2010

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

1 голос
/ 24 июня 2019

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

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

7 Параметры для захвата дампов кучи Java

1 голос
/ 24 марта 2010

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

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

0 голосов
/ 25 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...