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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Идея состоит в том, чтобы использовать "график" графика 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
...

Используя этот script:

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

Ответ 4

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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

7 вариантов захвата дампов Java Heap