Отладка на основе дампов

Инструмент Memory Analyzer позволяет диагностировать не только утечки памяти

Memory Analyzer — это мощный инструмент для выявления утечек памяти (memory leaks) и проблем с занимаемым в памяти пространством (footprint) по дампам Java™-процесса. Кроме того, этот инструмент обеспечивает разработчику детализированное понимание Java-кода и позволяет отлаживать многие сложные проблемы на основе одного только дампа, без вставки диагностического программного кода. Из этой статьи вы узнаете, как генерировать дампы и как использовать их для исследования состояния своего приложения.

Крис (Chris) Бейли (Bailey), Advisory Software Engineer, IBM

Крис Бейли (Chris Bailey) начал работать в Центре Технологий Java в IBM после окончания Университета в Саутгемптоне в 2000 году. Он много работает с пользователями над решением проблем, возникающих при работе с технологиями Java, выпущенными IBM, и продуктами, работающими на платформе Java. Крис является координатором форума разработчиков, под названием "Среда выполнения IBM Java и SDK" и в данный момент сосредоточил внимание на улучшении качества информации и инструментальных средств, доступных пользователям платформы Java от IBM.



Эндрю Джонсон, разработчик Java-инструментов и активный участник проекта Eclipse Memory Analyzer Tool, IBM

Andrew JohnsonЭндрю Джонсон (Andrew Johnson) — дипломированный инженер, инженер-консультант по программному обеспечению в центре IBM Java Technology Center в Херсли (Великобритания). Он работает в IBM с 1988 года, после получения степени бакалавра по электронной технике в Кембриджском университете (Великобритания). С 1996 года он занимается такими областями, как виртуальные машины Java, JIT-компиляторы и инструменты для диагностирования проблем в Java-решениях. Автор адаптера к продукту Eclipse Memory Analyzer, который читает дампы из виртуальных машин IBM. В настоящее время является одним из основных участников данного проекта.



Григоренко Григоренко, инженер по программному обеспечению, член специальной группы по продукту WebSphere Application Server, IBM

Author photoКевин Григоренко (Kevin Grigorenko) — инженером в специальной группе по продукту WebSphere Application Server, которая обеспечивает непосредственную и дистанционную дополнительную поддержку во всем мире по дефектам продуктов, особенно в критических для клиентов ситуациях. В настоящее время он занимается преимущественно выявлением проблем в продукте WebSphere Application Server и в стеке связанных продуктов, включая JVM и разные операционные системы. Кроме того, он имеет многолетний опыт разработки на различных платформах, в том числе Java Enterprise Edition, C, C++, Perl, PHP, Python, Ruby и .NET.



20.01.2012

Варианты инструмента Memory Analyzer

Инструмент Memory Analyzer из инструментария IBM Monitoring and Diagnostic Tools for Java распространяет диагностические возможности инструмента Eclipse Memory Analyzer Tool (MAT) на виртуальные машины IBM Virtual Machines for Java. Эта задача решается посредством расширения инструмента Eclipse MAT версии v. 1.0 с помощью среды IBM Diagnostic Tool Framework for Java (DTFJ). DTFJ позволяет осуществлять анализ «кучи» Java (heap) с помощью дампов на уровне операционной системы и дампов типа IBM Portable Heap Dump. Вариант от IBM доступен в составе пакета IBM Support Assistant (ISA). DTFJ-плагин доступен для автономного варианта применения инструмента Eclipse MAT. Информация о загрузке приведена в разделе Ресурсы.

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

Memory Analyzer — это межплатформенный инструмент с открытым исходным кодом, который можно использовать не только для диагностирования проблемы с памятью, но и для углубленного понимания состояния и поведения всего Java-приложения. Memory Analyzer читает т. н. «мгновенный дамп» (snapshot dump), созданный средой исполнения Java в процессе работы приложения, что предоставляет разработчику способ диагностирования замысловатых проблем, которые отладочный код может и не выявить.

Данная статья показывает, как генерировать дампы и как использовать их для исследования и диагностирования состояния приложения. Memory Analyzer позволяет исследовать потоки, объекты, пакеты и целые совокупности данных с целью устранения проблем Java-кода, не ограничивающихся только утечками памяти.

Типы мгновенных дампов

На данный момент инструмент Memory Analyzer способен работать со следующими тремя типами дампов.

  • IBM Portable Heap Dump (PHD). Этот дамп, представленный в закрытом формате IBM, содержит только тип/размер каждого Java-объекта в процессе, и отношения между объектами. Файл дампа в этом формате значительно меньше, чем в других форматах, и содержит наименьшее количество информации. Тем не менее этих данных обычно достаточно для диагностирования утечек памяти и для получения базового представления об архитектуре приложения и занимаемом им пространстве.
  • Бинарный дамп HPROF. Двоичный формат HPROF содержит все данные, присутствующие в формате IBM PHD, а также данные примитивов, содержащиеся внутри Java-объектов, и подробности потоков. Этот формат позволяет посмотреть на значения, содержащиеся в полях внутри объектов, и увидеть, какие методы выполнялись в момент совершения анализируемого дампа. Дополнительные данные примитивов значительно увеличивают размеры дампов в формате HPROF по сравнению с дампами в формате PHD; HPROF-дамп имеет приблизительно такой же размер, как используемая куча Java.
  • Системные дампы IBM. В тех случаях, когда используется среда исполнения Java от IBM, в инструмент Memory Analyzer может быть загружен собственный («нативный») файл дампа операционной системы — дамп памяти ядра на AIX® или Linux, малый дамп памяти на Windows® или дамп SVC на z/OS®. Эти дампы содержат образ всей памяти исполняемого приложения — всю информацию и все данные в формате HPROF, а также всю нативную память и всю информацию потоков. Файлы дампов в этом формате имеют самые большие размеры и содержат наиболее полную информацию.

Оба типа дампов от IBM доступны только при установленном плагине DTFJ (Diagnostic Tool Framework for Java) (см. раздел Ресурсы и врезку Варианты инструмента Memory Analyzer.

В таблице 1 обобщены различия между разными типами файлов дампов.

Таблица 1. Сводные характеристики по типам дампов
Формат дампаПриблизительный размер на дискеОбъекты, классы и загрузчики классов (classloader)Подробности потоковИмена полейСсылки на поля и на массивыПоля примитивов Содержание массива примитивовТочные корни для сборки мусораНативная память и потоки
IBM PHD20% от размера кучи JavaДаСовместно с Javacore*НетДаНетНетНетНет
HPROFРазмер как у кучи JavaДаДаДаДаДаДаДаНет
Системные дампы IBMРазмер на 30% больше, чему у кучи JavaДаДаДаДаДаДаДаДа

*При загрузке двух одновременно сгенерированных файлов — файла javacore.txt (файл дампа потока IBM) и файла heapdump.phd — инструмент Memory Analyzer обеспечивает доступ к подробностям потока в дампе формата IBM PHD.

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


Получение мгновенных дампов

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

Предварительные условия

Вне зависимости от типа дампа необходимо гарантировать для дампов достаточное дисковое пространство, чтобы они не подвергались усечению. По умолчанию дампы помещаются в текущий рабочий каталог процесса JVM. Для JVM-машины IBM это местоположение можно изменить с помощью опции командной строки -Xdump. Для JVM-машины HotSpot это местоположение можно изменить с помощью опции командной строки -XX:HeapDumpPath. Ссылки на соответствующий синтаксис приведены в разделе Ресурсы.

Дампы, сделанные с помощью операционной системы, могут быть использованы как для JVM-машины от IBM (IBM JVM), так и для JVM-машины HotSpot (JVM HotSpot). Для IBM JVM можно создавать дампы с помощью инструмента jextract (поставляемого вместе с JDK) и загружать их непосредственно в Memory Analyzer; для JVM HotSpot следует использовать инструмент jmap для извлечения дампов кучи (heap dump) из дампов ядра (core dump). Позднее мы подробно рассмотрим оба этих метода. Однако в некоторых операционных системах перед созданием дампа памяти необходимо гарантировать, что процесс работает с достаточным значением ulimit; в противном случае дамп памяти будет усеченным, а возможности анализа будут ограниченными. При некорректных значениях ulimit необходимо сначала их изменить, а затем перезапустить процесс и только после этого приступать к сборке дампа. В разделе Ресурсы приведены ссылки на подробную информацию о получении системных дампов при использовании операционных систем AIX, Linux®, z/OS и Solaris.

Получение мгновенного дампа: среды исполнения HotSpot

Среды исполнения Java на основе HotSpot генерируют только дамп в формате HPROF. В распоряжении разработчика имеется несколько интерактивных методов генерации дампов и один метод генерации дампов на основе событий.

  • Интерактивные методы:
    • Использование Ctrl+Break. : Если для исполняющегося приложения установлена опция командной строки -XX:+HeapDumpOnCtrlBreak, то дамп формата HPROF генерируется вместе с дампом потока при наступлении события Ctrl+Break или SIGQUIT (обычно генерируется с помощью kill -3), которое инициируется посредством консоли. Эта опция может быть недоступна в некоторых версиях. В этом случае попытайтесь использовать следующую опцию:

      -Xrunhprof:format=b,file=heapdump.hprof
    • Использование инструмента jmap. : Утилита jmap (см. раздел Ресурсы), поставляемая в составе каталога bin комплекта JDK, позволяет запрашивать дамп HPROF из исполняющегося процесса. В случае Java 5 используйте следующий синтаксис:

      jmap -dump:format=b pid

      В случае Java 6 используйте следующую версию, где live— это опция, в результате которой в файл дампа процесса с идентификатором ID (PID) записываются только «живые» объекты:

      jmap -dump[live,]format=b,file=filename pid
    • Использование операционной системы. : Для создания файла ядра воспользуйтесь неразрушающей командой gcore или разрушающими командами kill -6 или kill -11. Затем извлеките дамп кучи из файла ядра с помощью утилиты jmap:

      jmap -dump:format=b,file=heap.hprof path to java executable core
    • Использование инструмента JConsole. Операция dumpHeap предоставляется в JConsole как MBean-компонент HotSpotDiagnostic. Эта операция запрашивает генерацию дампа в формате HPROF.
  • Метод на основе событий
    • Событие OutOfMemoryError: Если для исполняющегося приложения установлена опция командной строки -XX:+HeapDumpOnOutOfMemoryError, то при возникновении ошибки OutOfMemoryError генерируется дамп формата HPROF. Это идеальный метод для «производственных» систем, поскольку он практически обязателен для диагностирования проблем памяти и не сопровождается постоянными накладными расходами с точки зрения производительности. В старых выпусках сред исполнения Java на базе HotSpot для этого события не устанавливается предельное количество дампов кучи в пересчете на одну JVM; в более новых выпусках допускается не более одного дампа кучи для этого события на каждый запуск JVM.

Получение мгновенного дампа: среды исполнения IBM

Среды исполнения IBM предоставляют механизмы создания дампов и трассировки, которые позволяют генерировать дампы в формате PHD или системные дампы в большом количестве интерактивных и основанных на событиях сценариев. Кроме того, генерировать интерактивные дампы можно с помощью инструмента Health Center или программным способом — с помощью API-интерфейса Java.

  • Интерактивные методы.
    • Использование SIGQUIT или Ctrl+Break: Когда в среду исполнения IBM посылается команда Ctrl+Break или SIGQUIT (обычно генерируется с помощью kill -3), в механизме дампа IBM генерируется пользовательское событие. По умолчанию это событие генерирует только файл дампа потока (javacore.txt). Можно использовать опцию -Xdump:heap:events=user для генерации дампа в формате PHD или опцию -Xdump:system:events=user для генерации системного дампа Java-приложения.

    • Использование операционной системы для создания системного дампа

      • AIX: команда gencore (или разрушающая команда kill-6 или kill-11)
      • Linux/Solaris: команда gencore (или разрушающая команда kill-6 или kill-11)
      • Windows: userdump.exe
      • z/OS: SVCDUMP или консольный дамп
    • Использование инструмента IBM Monitoring and Diagnostics Tools for Java — Health Center. В меню инструмента Health Center есть опция для запроса дампа в формате PHD или системного дампа из рабочего процесса Java (см. раздел Ресурсы).

  • Методы на основе событий. Механизмы IBM для дампов и трассировки предоставляют гибкий набор возможностей по генерации PHD-дампов и системных дампов для большого числа событий – от выдаваемых исключений до выполняемых методов. Это позволяет генерировать дампы для большинства сценариев проявления проблем, которые необходимо диагностировать:
    • Использование механизма IBM для генерации дампов. Механизм генерации дампов предоставляет большое количество событий, при наступлении которых можно генерировать PHD-дамп или системный дамп. Кроме того, он позволяет осуществлять фильтрацию по типам этих событий для более детального управления выбором момента генерации дампа.

      Использование опции -Xdump:what позволяет увидеть события по умолчанию. Например, можно увидеть, что файлы heapdump.phd и javacore.txt создаются для первых четырех исключений типа OutOfMemoryError в JVM.

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

      -Xdump:heap:none -Xdump:java+system:events=systhrow,
       filter=java/lang/OutOfMemoryError,range=1..4,request=exclusive+compact+prepwalk

      Некоторые исключения, например, NullPointerException, повсеместно генерируются в самых разных приложениях и в самом разнообразном коде. Это затрудняет генерацию дампа по конкретному интересующему вас исключению NullPointerException. Чтобы конкретизировать исключение, по которому следует генерировать дамп, для событий throw (выдать) и catch (перехватить) предоставляется дополнительный уровень фильтрации, позволяющий специфицировать методы выдачи и перехвата исключений соответственно. Эта задача решается посредством добавления разделителя #, за которым следует метод выдачи и перехвата. Например, эта опция генерирует системный дамп, если исключение NullPointerException выдал метод bad():

      -Xdump:system:events=throw,
             filter=java/lang/NullPointerException#com/ibm/example/Example.bad

      Следующая опция генерирует системный дамп в том случае, когда метод catch() перехватил исключение NullPointerException:

      -Xdump:system:events=catch,
             filter=java/lang/NullPointerException#com/ibm/example/Example.catch

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

      -Xdump:system:events=throw, filter=java/lang/NullPointerException,range=5

      Следующая опция задает диапазон, чтобы генерировать дамп только при втором, третьем и четвертом появлении исключения NullPointerException:

      -Xdump:system:events=throw, filter=java/lang/NullPointerException,range=2..4

      Наиболее полезные события и фильтры обобщены в таблице 2


      Таблица 2. Доступные события для генерации дампов
      СобытиеОписаниеДоступная фильтрацияПример
      gpfОбщее нарушение защиты (аварийный отказ)-Xdump:system:events=gpf
      userСигнал, генерированный пользователем (SIGQUIT или Ctrl+Break)-Xdump:system:events=user
      vmstopЗавершение работы VM, включая вызов System.exit()По коду завершения-Xdump:system:events=vmstop,filter=#0..#10
      Генерация системного дампа при завершении работы VM с кодом завершения в диапазоне 0 и 10.
      loadЗагрузка классаИмя класса-Xdump:system:events=load,filter=com/ibm/example/Example
      Генерация системного дампа в случае загрузки класса com.ibm.example.Example.
      unloadВыгрузка классаПо имени класса-Xdump:system:events=unload,filter=com/ibm/example/Example
      Генерация системного дампа в случае выгрузки класса com.ibm.example.Example.
      throwБросание исключенияПо имени класса с исключением-Xdump:system:events=throw,filter=java/net/ConnectException
      Генерация системного дампа в случае генерации исключения ConnectException.
      catchПоимка исключенияПо имени класса с исключением-Xdump:system:events=catch,filter=java/net/ConnectException
      Генерация системного дампа в случае поимки исключения ConnectException.
      systhrowJVM собирается бросить Java-исключение (эта ситуация отличается от события throw, поскольку она инициируется только для ошибок, обнаруженных внутри JVM) По имени класса с исключением-Xdump:system:events=systhrow,filter=java/lang/OutOfMemoryError
      Генерация системного дампа в случае генерации ошибки OutOfMemoryError.
      allocationВыделение Java-объектаПо размеру выделяемого объекта-Xdump:system:events=allocate,filter=#5m
      Генерация системного дампа в случае выделения объекта размером более 5 МБ.
    • Использование механизма трассировки IBM. Механизм трассировки позволяет инициировать PHD-дампы и системные дампы для любого Java-метода, работающего в приложении, при входе в метод или выходе из метода. Эта задача решается посредством использования ключевого слова trigger в опциях командной строки -Xtrace, которые управляют механизмом трассировки IBM. Синтаксис для опции инициирования:
      method{methods[,entryAction[,exitAction[,delayCount[,matchcount]]]]}

      Добавление следующей опции командной строки к приложению генерирует системный дамп в случае вызова метода Example.trigger ():

      -Xtrace:maximal=mt,trigger=method{com/ibm/example/Example.trigger,sysdump}

      Следующая опция командной строки генерирует PHD-дамп в случае вызова метода Example.trigger ():

      -Xtrace:maximal=mt,trigger=method{com/ibm/example/Example.trigger,heapdump}

      Тем не менее рекомендуется задавать диапазон таким образом, чтобы дампы создавались не при каждом вызове соответствующего метода. В следующем примере первые пять вызовов Example.trigger() игнорируются, а затем инициируется один дамп:

      -Xtrace:maximal=mt,trigger=method{com/ibm/example/Example.trigger,sysdump,,5,1}

      Обратите внимание, что в этом примере для exitAction используется пустой терм, поскольку мы инициируем дампы только по входу в метод.
  • Программируемые методы: Кроме того, среды исполнения IBM предоставляет класс com.ibm.jvm.Dump с методами javaDump (), heapDump () и systemDump (). Эти методы генерируют дампы потока, PHD-дампы и системные дампы, соответственно.

Получение дампа с помощью инструмента Memory Analyzer

В дополнение к методам для получения дампов, которые предоставляются самими средами исполнения, инструмент Memory Analyzer поддерживает функцию Acquire Heap Dump (см. рис. 1), которая позволяет инициировать и загрузить мгновенный дамп из Java-процесса, исполняющегося на той же машине, что и Memory Analyzer:

Рис. 1. Функция Acquire Heap Dump инструмента Memory Analyzer
Screen shot showing the Acquire Heap Dump function in Memory Analyzer

В средах исполнения на базе HotSpot инструмент Memory Analyzer генерирует дампы с помощью jmap. В средах исполнения IBM дамп генерируется с помощью функциональности Java под названием late attach и программируемого API-интерфейса. Для работы этой функции требуется выпуск Java 6 SR6 (предшествующие выпуски не содержат функции late attach).

Требования к постобработке

В случае системных дампов IBM каждый дамп необходимо подвергнуть постобработке с помощью инструмента jextract, поставляемого в составе JDK:

jextract core

В идеальном случае инструмент jextract выполняется на той же физической машине, которая произвела дамп; используется инструмент jextract из той же установки JDK, которая произвела дамп; доступ по чтению имеется к тем же библиотекам, с которыми исполнялся процесс java. Необходимо учитывать, что при обработке дампа инструмент jextract может потреблять значительные процессорные ресурсы, что может быть недопустимо во многих производственных системах. В этом случае следует обрабатывать дамп на наиболее близкой по характеристикам системе, например, на системе предпроизводственного тестирования. Версии сред исполнения Java Service Refresh (SR) и Fix Pack (FP) на этих системах должны совпадать.

Инструмент jextract создает ZIP-файл, который содержит исходный дамп ядра, обработанное представление этого дампа, исполняемую Java-программу и библиотеки, использовавшиеся процессом java. После завершения работы инструмента jextract можно удалить исходный (неархивированный) дамп ядра. Результирующий ZIP-файл следует загрузить в Memory Analyzer.

Из системного дампа, обработанного инструментом jextract, можно извлечь PHD-дамп. Для этого следует загрузить результирующий ZIP-файл в инструмент jdmpview и выполнить команду heapdump (см. раздел Ресурсы).


Использование инструмента Memory Analyzer для анализа проблем

Инструмент Memory Analyzer способен диагностировать ошибки типа OutOfMemoryError посредством отыскания в приложении областей, имеющих утечку памяти или требующих пространства больше, чем имеется доступной памяти. Memory Analyzer автоматически обнаруживает утечки и генерирует отчет Leak Suspects (см. раздел Ресурсы).

Кроме того, дополнительные данные, доступные в HPROF-дампах и в системных дампах IBM, особенно имена полей и значения полей, в сочетании с возможностями представления Inspector и языка OQL (Object Query Language) позволяют диагностировать более широкий диапазон проблем, а не только выявлять проблемы типа «Чем занята вся память?». Например, можно выяснить занятость и загруженность коллекций, чтобы увидеть, насколько эффективно заданы их размеры, или установить имя хоста и порт, ассоциированные с исключением ConnectException, чтобы увидеть, какое соединение пыталось установить исследуемое приложение.

Рассмотрение полей в объекте с помощью представления Inspector

Когда какой-либо объект выбран в инструменте Memory Analyzer, представление Inspector показывает имеющуюся информацию по этому объекту, включая иерархию классов, атрибуты и «статику». Панель Attributes показывает поля экземпляра и значения, связанные с объектом, а панель Statics показывает статические поля и значения, ассоциированные с классом.

На рис. 2 показано представление Inspector для простого объекта java.net.URL, которое позволяет увидеть подробности об этом объекте, включая тип протокола для URL и место назначения:

Рис. 2. Панели Statics, Attributes и Class Hierarchy в представлении Inspector
Screen shot showing the three panels in the Inspector view

В панели Attributes на рисунке 2, можно увидеть, что объект URL ссылается на файл JAR (поле protocol), находящийся в локальной файловой системе (его местоположение специфицируется полями path и file).

Выполнение запросов к объектам с помощью языка OQL

Для исследования дампа с помощью специально создаваемых SQL-подобных запросов можно использовать язык OQL. Этой тема заслуживает отдельной статьи, поэтому мы ограничимся несколькими примерами. Для получения дополнительной информации обратитесь к справочной системе (Help) инструмента Memory Analyzer и перейдите в раздел, посвященный языку OQL.

OQL особенно полезен для отслеживания исходящих ссылок и полей набора объектов до определенного поля. Например, если у класса A есть поле foo типа B, а у класса B есть поле bar, которое имеет тип String, то простой запрос для отыскания всех объектов String будет иметь следующий вид:

SELECT aliasA.foo.bar.toString()
FROM A aliasA

Мы даем классу A псевдоним aliasA, на который затем ссылаемся в операторе SELECT. Этот запрос выбирает исключительно из экземпляров класса A. Если бы мы хотели выбирать из всех экземпляров класса A, а также из любых подклассов, мы бы использовали следующий запрос:

SELECT aliasA.foo.bar.toString()
FROM INSTANCEOF A aliasA

Рассмотрим более сложный пример с несколькими DirectByteBuffer:

SELECT k, k.capacity
FROM java.nio.DirectByteBuffer k
WHERE ((k.viewedBuffer=null)and(inbounds(k).length>1))

В данном случае мы хотим получить поле емкости любого буфера DirectByteBuffer, которое содержит сведения о нативной памяти, удерживаемой соответствующим объектом. Мы также хотим отфильтровать все буферы DirectByteBuffer, у которых поле viewedBuffer содержит значение null (поскольку это только представления других буферов DirectByteBuffer), и которые имеют более одной входящей ссылки (чтобы не видеть буферов с фантомными ссылками, ожидающими очистки, поскольку нам нужны только «живые» буферы DirectByteBuffer).

Выполнение сравнений между представлениями или между дампами

Memory Analyzer позволяет сравнивать таблицы, сгенерированные запросами. Эти таблицы могут быть из одного дампа (что позволяет увидеть, присутствуют ли объекты String из одного представления в объекте, являющемся элементом какой-либо коллекции, которая видна в другом представлении) или из разных дампов (что позволяет искать изменения в данных, например, рост количества коллекций).

Чтобы выполнить сравнение, нужно добавить соответствующие таблицы в Compare Basket (корзина для сравнения), а затем запросить сравнение записей в этой «корзине». Сначала найдите и выберите запись для нужной таблицы в представлении Navigation History, а затем в контекстном меню выберите опцию Add to Compare Basket (добавить в корзину) (см. рис. 3).

Рис. 3. Добавление таблиц из представления Navigation History в Compare Basket
Screen shot showing the 'Add to Compare Basket' function in Memory Analyzer

После того, как в Compare Basket будут присутствовать две записи, можно выполнить сравнение с помощью кнопки Compare-the-results (красный восклицательный знак) в правом верхнем углу панели (см. рис. 4).

Рис. 4. Сравнение записей, содержащихся в Compare BasketBasket
Screen shot showing the 'Compare the results' function in Memory Analyzer

Занимаемое пространство и эффективность использования памяти

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

Первый шаг — генерация отчета Top Components. Он разделяет потребление памяти в системе по компонентам, анализирует использование памяти в каждом компоненте и отыскивает наиболее расточительные. Про объект, над которым доминирует другой объект, можно сказать, что он принадлежит этому доминирующему объекту. В отчете Top Components перечисляются все объекты, не принадлежащие никакому другому объекту. Это и есть главные доминирующие объекты кучи. Затем главные доминирующие объекты с помощью классов объектов делятся по загрузчикам классов (classloader), после чего все эти главные доминирующие объекты и принадлежащие им объекты назначаются соответствующим загрузчикам классов. Дальнейший анализ осуществляется посредством выбора одного classloader-загрузчиков в отчете с целью открытия нового отчета по компонентам для этого загрузчика.

Для каждого компонента анализируются объекты Collections. Классы Collections, как показывает java.util.*, предлагают программистам отличные возможности для экономии рабочего времени. Они предоставляют хорошо протестированные реализации для списков, наборов и отображений. Среднее приложение может иметь миллионы коллекций, вследствие чего потери памяти в коллекциях могут быть весьма значительными.

Пустые коллекции –весьма распространенная причина бесполезного расходования памяти. Артефакты ArrayList, Vector, HashMap и HashSet создаются со вспомогательными массивами для хранения записей, размер каждого из которых по умолчанию может составлять, например, 10 записей. Удивительно распространенным явлением в приложениях является создание коллекций, в которых не хранятся никакие объекты. Такой подход может привести к быстрому израсходованию всей доступной памяти. Например, при наличии 100000 пустых коллекций одни только вспомогательные массивы способны потребить 100000 * (24+10*4) байт = 6 МБ.

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

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

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

Дублирующиеся строки

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

Эту проблему можно решить посредством использования String.intern() или поддержания пользовательских структур типа hash set или hash map.

Потери в массивах char

Конструкция String.substring() реализуется на языке Java посредством создания нового объекта String, совместно использующего исходный символьный массив. Это эффективный подход в том случае, если исходная строка по-прежнему необходима. Если необходима лишь небольшая подстрока, то некоторое пространство тратится впустую, поскольку символьный массив сохраняется целиком. Запрос Waste In Char Arrays показывает объем теряемого пространства в символьных массивах, на которые ссылаются только строки.


Пакеты Eclipse и иерархия classloader-загрузчиков

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

Запрос Class Loader Explorer показывает все classloader-загрузчики в системе и делает это для всех приложений. Этот запрос показывает классы, загруженные classloader-загрузчиком, а также родительскую цепочку этого classloader-загрузчика, что позволяет понять проблемы, возникающие при загрузке классов. Инспектирование позволяет увидеть, существует ли несколько копий некоторого classloader-загрузчика. Если для классов, не имеющих экземпляров, задан classloader-загрузчик, то вероятнее всего этот classloader-загрузчик неактивен.

Запрос на наличие дублирующихся классов показывает имена классов, которые были загружены несколькими classloader-загрузчиками. Это может быть признаком утечки памяти, порожденной classloader-загрузчиком. Чтобы в classloader-загрузчике произошла утечка памяти, достаточно всего одной ссылки на объект, находящийся где-то в другом месте системы, например, в реестре. Этот объект содержит ссылку на свой класс, класс содержит ссылку на classloader-загрузчик, а classloader-загрузчик содержит ссылки на все определенные классы.

OSGi — это общепринятая спецификация для инфраструктуры загрузки классов. Одна из реализаций этой спецификации под названием Eclipse Equinox применяется в основанных на Eclipse приложений для разделения подключаемых модулей, а также в продукте WebSphere® Application Server начиная с версии 6.1. При попытке понять состояние некоторого приложения весьма полезно знать состояние всех пакетов. Обратимся к рисунку 5, на котором показан запрос Eclipse Equinox Bundle Explorer.

Рис. 5. Eclipse Bundle Explorer
A screen shot showing the Bundle Explorer view

Системный дамп или HPROF-дамп содержит все объекты и все поля. Представление Bundle Explorer показывает все пакеты в системе, а также их состояния вместе с их зависимостями, зависимыми объектами и сервисами. Оно может показать разработчику пакеты, которые неожиданно для него являются активными и как таковые потребляют больше ресурсов.


Использование данных потоков

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

Представление Thread Overview

Представление Thread Overview (см. рис. 6) показывает все потоки в JVM-машине, а также различные атрибуты потоков, такие как размер удерживаемой кучи, контекст classloader, приоритет, состояние и собственный идентификатор ID.

Рис. 6. Представление Thread Overview
Screen shot showing the Thread Overview view

Знание размера удерживаемой кучи особенно полезно в случаях, когда при возникновении ошибки OutOfMemoryError проблемы с кучей Java по существу отсутствуют, но сумма удерживаемых потоков становится «слишком большой». В данном случае JVM-машина может иметь недостаточные размеры, размеры пула потока могут быть слишком большими или средняя/максимальная «загрузка» кучи Java для потока может быть слишком высока.

Представление Thread Stacks

Представление Thread Stacks (рис. 7) показывает каждый поток, его стек, фреймы этого стека и локальные переменные Java в этих фреймах стека.

Рис. 7. Представление Thread Stacks
Screen shot showing the Thread Stacks view

Представление Thread Details

И в представлении Thread Overview, и в представлении Thread Stacks можно щелкнуть правой кнопкой по потоку и выбрать опцию Thread Details (подробности потока) в верхней части меню или последовательно выбрать Java Basics > Thread Details. Представление Thread Details дает более подробную информацию о потоке (если она доступна), например, о нативном стеке.

В примере на рисунке 7, развернут поток типа java.lang.Thread с именем main— это главный поток в простой программе на основе командной строки. Показан каждый фрейм стека данного потока, при этом фреймы с доступными локальными переменными Java могут быть развернуты. В данном случае String передается как аргумент из Play.method1 в Play.method2, а содержимое этой строки, user1, обведено красным кругом. Представьте себе, какие мощные возможности для реконструкции или реверсивного проектирования того, что происходило в момент совершения дампа, открывают знания о том, что происходило в каждом фрейме стека потока и в каких объектах это все происходило..

Обратите внимание, что вследствие оптимизации среды исполнения будут доступны не все связанные объекты, такие как параметры методов или экземпляры объектов (хотя эти объекты будут присутствовать в дампе), однако те объекты, с которыми «активно работают», обычно оказываются доступными.


Анализ исключений

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

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

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

Инструмент Memory Analyzer имеет доступ к полям внутри объектов, что позволяет отыскать сообщение об исключении, начиная с объекта исключения. Кроме того, в некоторых случаях можно извлечь дополнительные данные, которые отсутствовали в исходном исключении.

Определение местонахождения исключений в моментальном дампе

Один из способов нахождения исключений, присутствующих в моментальном дампе, состоит в использовании возможностей языка OQL в инструменте Memory Analyzer для отыскания в дампе интересующих нас объектов. Например, данный запрос находит все объекты исключений:

SELECT * 
FROM INSTANCEOF java.lang.Exception exceptions

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

SELECT exceptions.@displayName, exceptions.detailMessage.toString() 
FROM INSTANCEOF java.lang.Exception exceptions

Результаты предыдущего запроса показаны на рис. 8.

Рис. 8. Результаты OQL-запроса по исключениям, содержащие сообщения об исключениях
Screen shot showing the output from the OQL query

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

Извлечение дополнительной информации, касающейся исключений

Отыскание в дампе объекта исключения позволяет восстановить сообщение об этом исключении, однако иногда сообщение об исключении имеет слишком общий или слишком неопределенный характер, что не способствует пониманию причин проблемы. В качестве примера рассмотрим исключение java.net. ConnectException. При попытке создать сокетное соединение с хостом, который не принимает соединений, мы получили следующее сообщение:

java.net.ConnectException: Connection refused: connect
     at java.net.PlainSocketImpl.socketConnect(Native Method)
     at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:352)
     at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:214)
     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:201)
     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:377)

     at java.net.Socket.connect(Socket.java:530)
     at java.net.Socket.connect(Socket.java:480)
     at java.net.Socket.(Socket.java:377)
     at java.net.Socket.(Socket.java:220)

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

Трассировка стека должна включать объект сокета, содержащий полезные данные. Если мы сможем с помощью Memory Analyzer найти этот объект сокета в моментальном дампе, то сможем узнать имя хоста/порт системы, отказавшиеся от соединения.

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

-Xdump:system:events=throw,range=1..1,
       filter=java/net/ConnectException#java/net/PlainSocketImpl.socketConnect

Эта опция генерирует системный дамп IBM при первом возникновении исключения ConnectException, сгенерированного методом PlainSocketImpl.socketConnect().

После загрузки сгенерированного моментального дампа в Memory Analyzer мы сможем с помощью опции > Java Basics > Thread Stacks сформировать список потоков и объектов, связанных с каждым методом в трассировке стека потоков.

Развернув текущий поток и фреймы методов в этом потоке, мы сможем увидеть объекты, ассоциированные с этими методами. В случае исключения java.net.ConnectException самым интересным является метод java.net.Socket.connect(). Развернув фрейм этого метода, мы увидим ссылку на объект java.net.Socket в памяти. Это и есть то соединение через сокет, которое мы пытались установить.

После выбора объекта Socket соответствующие поля отображаются в представлении Inspector, как показано на рис. 9.

Рис. 9. Представление Inspector для объекта Socket
Screen shot showing the Inspector view for a Socket object

Информация на рис. 9 не слишком полезна, поскольку фактическая реализация Socket находится в поле impl. Чтобы проинспектировать содержимое объекта impl, можно или развернуть объект Socket и выбрать строку impl java.net.SocksSocketImpl в главной панели, или в представлении Inspector нажать правой кнопкой на поле impl и выбрать опцию Go Into. Теперь поля для SocksSocketImpl видны в представлении Inspector, как показано на рис. 10.

Рис. 10. Представление Inspector для объекта SocksSocketImpl
Screen shot showing the Inspector view for a Socket Impl object

Представление на рис. 10 обеспечивает доступ к полям address и port. В данном случае поле port имеет значение 100, однако поле address указывает на объект java.net.Inet4Address. После выполнения аналогичной процедуры для рассмотрения полей объектов Inet4Address мы получим результаты, показанные на рис. 11:

Рис. 11. Представление Inspector для объекта Inet4Address
Screen shot showing the Inspector view for a Inet4Address object

Несложно увидеть, что поле hostName имеет значение baileyt60p.


Подсказки и советы

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

  • Не забывайте, что сам инструмент Memory Analyzer может исчерпать память. В случае использования Eclipse MAT отредактируйте значение -Xmx в файле MemoryAnalyzer.ini. В случае использования версии ISA отредактируйте файл ISA Install/rcp/eclipse/plugins/com.ibm.rcp.j2se.../jvm.properties.
  • Если вам по-прежнему не хватает памяти для 32-разрядной версии инструмента Memory Analyzer, используйте 64-разрядную версию Eclipse MAT или попробуйте headless-режим (см. Ресурсы). (Инструмент ISA в настоящий момент не поддерживает 64-разрядный режим).
  • Memory Analyzer записывает файлы подкачки в каталог дампа, что уменьшает время перезагрузки дампа. Эти файлы могут быть заархивированы, отосланы на другую машину и помещены в тот же каталог, где находится сам дамп, что избавляет от необходимости полной перезагрузки дампа.
  • Если размеры дампа расходятся с данными сборщика мусора на момент осуществления дампа, обратитесь по ссылке Unreachable Objects Histogram на вкладке Overview. Возможно, куча Java содержала много мусора (например, если какая-либо «штатная» коллекция не исполнялась на протяжении некоторого промежутка времени), который был удален инструментом Memory Analyzer.
  • Если два объекта A и B не имеют прямых ссылок друг на друга, но у обоих из них есть исходящие ссылки на некоторый набор объектов C, то удерживаемая набором C куча будет включена не в наборы A или B, а в набор объекта, доминирующего над объектами A и B. В некоторых ситуациях B может временно наблюдать за набором C, который фактически является потомком A. В этом случае можно щелкнуть правой кнопкой на A и выбратьJava Basics > Customized Retained Set, после чего использовать адрес B как параметр исключения (-x).
  • Можно одновременно загрузить несколько дампов и сравнить их. Откройте опцию Histogram для более «свежего» дампа, нажмите на кнопку Compare в верхней части окна и выберите базовый дамп.
  • При рассмотрении дерева ссылок помните, что ссылка способна обращаться, прямо или косвенно, обратно к «родительской» ссылке, в результате чего ваше исследование может войти в замкнутый цикл (например, в связанном списке). Не забывайте об объектных адресах. Кроме того, помните, что если перед именем класса объекта стоит слово class, значит, вы исследуете статический экземпляр этого класса.
  • В большинстве представлений отображаемое значение String ограничено 1024 символами. Если вам необходима вся строка, нажмите правой кнопкой на объекте и выберите Copy > Save value to file.
  • У большинства представлений есть опция экспорта, а большая часть HTML-результатов создается в файловой системе. Это позволяет экспортировать данные для совместного использования или для дальнейшего преобразования. Кроме того, можно нажать сочетание клавиш Ctrl+C на любых, предварительно выделенных строках, чтобы скопировать текстовое представление этих строк в буфер обмена.

Согласно описанию на ресурсе Eclipse.org, инструмент Memory Analyzer был первоначально разработан как «быстрый и функционально насыщенный анализатор кучи Java, который помогал бы находить утечки памяти и уменьшать потребление памяти». Однако возможности данного инструмента несомненно простираются далеко за пределы этого описания. В дополнение к своей роли при диагностировании «нормальных» проблем с памятью мгновенные дампы могут быть использованы в качестве альтернативы или в качестве дополнения для других методик выявления проблем, таких как трассировка и «наложение заплат». В частности, при использовании HPROF-дампов и системных дампов от IBM инструмент Memory Analyzer предоставляет содержимое памяти, включая примитивы и имена полей из первоначального исходного кода. С помощью различных представлений, рассмотренных в данной статье, можно исследовать или реконструировать имеющуюся проблему, включая такие аспекты, как общее занимаемое пространство и эффективность использования памяти, пакеты Eclipse и отношения classloader-загрузчиков, использование данных потока и локальные переменные фрейма стека, исключения и т.д. Кроме того, язык OQL и модель подключаемых модулей Memory Analyzer упрощают исследование дампа с помощью языка запросов и программируемых методов, что помогает автоматизировать типовые задачи анализа.

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Open source
ArticleID=788575
ArticleTitle=Отладка на основе дампов
publish-date=01202012