Итак, мы сделали дамп JVM на 150 Гб. Что дальше?

Возможность сделать снимок (или дамп) памяти виртуальной машины Java — это инструмент, ценность которого сложно переоценить. Файл дампа содержит копии всех Java объектов, находившихся в памяти в момент снимка. Формат файла хорошо известен, и существует множество инструментов, которые умеют с ним работать.
ed270f028c22814cc480f590c7650408.jpg

В моей практике анализ дампов JVM не раз помогал найти причины сложных проблем.

Однако дампы бывают разные. В этот раз передо мной — дамп размером 150 Гб. Моя задача — анализ проблемы, выявленной в процессе, который стал источником этого дампа.

Приложение, в котором я ищу проблему — это гибрид СУБД и системы непрерывной обработки данных. Все данные хранятся в памяти в виде Java объектов, поэтому размер «кучи» может достигать внушительных размеров (личный рекорд — 400 Гб).

Обычно для работы с небольшими дампами я использую JVisualVM. Но полагаю, что дамп такого размера не по зубам ни JVisualVM, ни Eclipse Memory Analyzer, ни другим профайлерам (хотя пробовать я не стал). Даже копирование файла такого объёма с сервера на локальный диск уже представляет проблему.

При анализе дампов в JVisualVM я часто прибегал к возможности использовать JavaScript для программного анализа графа объектов. Графические инструменты хороши, но пролистывать миллионы объектов — не самое приятное занятие. Гораздо приятнее исследовать граф объектов при помощи кода, а не мыши.

Дамп JVM — это всего лишь сериализованный граф объектов; моя задача — извлечь из этого графа конкретную информацию. Мне не очень нужен красивый пользовательский интерфейс: API для работы с графом объектов программным путём — вот инструмент, который на самом деле мне нужен.

Как программно проанализировать дамп «кучи»?

Я начал свое исследование с профайлера NetBeans. NetBeans основан на открытом коде и имеет визуальный анализатор дампа «кучи» (этот же код используется в JVisualVM). Код для работы с дампом JVM является отдельным модулем, а предоставляемый им API вполне подходит для написания собственных специализированных алгоритмов анализа.

Однако у анализатора дампов NetBeans имеется принципиальное ограничение. Библиотека использует временный файл для построения вспомогательного индекса по дампу. Размер индексного файла, как правило, составляет около 25% от размера дампа. Но самое важное — для построения этого файла требуется время, и любой запрос по графу объектов возможен только после того, как индекс построен.

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

Еще одним важным изменением по сравнению с исходной библиотекой было добавление HeapPath нотации.

HeapPath — это язык выражений для описания путей в графе объектов, он заимствует некоторые идеи у XPath. Он полезен как в качестве универсального языка предикатов в механизмах обхода графов, так и в качестве простого инструмента для извлечения данных из дампа объектов. HeapPath автоматически конвертирует строки, примитивы и некоторые другие простые типы из структур дампа JVM в обычные Java объекты.

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

Конечно, для интерактивного «свободного поиска» API + Java является не лучшим инструментом. Однако он дает мне возможность делать свою работу, а размер дампа в 150 Гб не оставляет выбора.

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

Кстати: Один проход «кучи» в 150 Гб занимает около 5 минут. Реальный анализ, как правило, требует нескольких проходов, но даже с учётом этого время обработки является приемлемым.

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

На GitHub есть примеры по анализу дампов JBoss сервера.

  • Анализ распределения объектов в Web сессиях
  • Реконструкция дерева JSF компонентов

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

  • 1 марта 2017 в 12:43 (комментарий был изменён)

    0

    А вы не рассматривали вариант при таких объёмах сделать полный coredump процесса, а потом анализировать его с помощью Serviceability Agent, где уже есть всё необходимое API для вытаскивания классов, тредов, обхода хипа и т. д.?

© Habrahabr.ru