четверг, 10 мая 2012 г.

java.lang.OutOfMemory: JDK build-in analysis tools

Недавно услышал от одного из менеджеров удивлённое восклицание - "java и утечка памяти?! Как такое может быть?! В java же ведь нет утечек памяти!!!". К сожалению, этот менеджер был не прав, и утечки памяти - memory leak - в java всё-таки встречаются.
(Понимание причин требует знания модели памяти и работы сборщика мусора, в этом посте они не рассматриваются; в качестве обзора можно посмотреть, какие бывают типы OutOfMemoryError или из каких частей состоит память java процесса)
Что же делать, если в один не очень прекрасный день в логе приложения обнаруживается строка "java.lang.OutOfMemoryError: Java heap space"?
Некоторую помощь - иногда достаточно полезную, а иногда единственно возможную - могут предоставить встроенные в JDK средства. Далее следует краткий обзор таких средств с полезными ссылками.




----------------------------------------------------------------------------------------------------
HPROF: build-in tool, который позволяет профилировать память и процессор при выполнении java программы.
A Heap/CPU Profiling Tool in J2SE 5.0
Работает через JVMTI (java virtual machine tool interface).
Данные сохраняет в файл txt/bin. Текстовый формат удобен для просмотра и анализа вручную, бинарный позволит использовать средство автоматического анализа. Может отправлять данные по сети.
Формат запуска профилируемого приложения:
    java -agentlib:hprof[=options] ToBeProfiledClass (например, -agentlib:hprof=heap=sites)
        или
    java -Xrunhprof[:options] ToBeProfiledClass
Команда java -agentlib:hprof=help покажет список дополнительных опций.

Пример результата работы утилиты в текстовом виде:

JAVA PROFILE 1.0.1, created Tue Apr 10 13:17:12 2012
Header for -agentlib:hprof (or -Xrunhprof) ASCII Output (JDK 5.0 JVMTI based)
@(#)jvm.hprof.txt    1.5 06/01/28
Copyright (c) 2006 Sun Microsystems, Inc. All  Rights Reserved.
WARNING!  This file format is under development, and is subject to change without notice.
This file contains the following types of records:

THREAD START
THREAD END  mark the lifetime of Java threads
TRACE       represents a Java stack trace.  Each trace consists
            of a series of stack frames.  Other records refer to
            TRACEs to identify (1) where object allocations have
            taken place, (2) the frames in which GC roots were
            found, and (3) frequently executed methods.
HEAP DUMP   is a complete snapshot of all live objects in the Java
            heap.  Following distinctions are made:
            ROOT    root set as determined by GC
            CLS     classes
            OBJ     instances
            ARR     arrays
SITES       is a sorted list of allocation sites.  This identifies
            the most heavily allocated object types, and the TRACE
            at which those allocations occurred.
........пропущено   

HEAP DUMP, SITES, CPU SAMPLES|TIME and MONITOR DUMP|TIME records are generated
at program exit.  They can also be obtained during program execution by typing
Ctrl-\ (on Solaris) or by typing Ctrl-Break (on Win32).
--------
THREAD START (obj=50000147, id = 200002, name="HPROF gc_finish watcher", group="system")
THREAD START (obj=5000014b, id = 200001, name="main", group="main")
THREAD START (obj=5000136e, id = 200003, name="Monitor Ctrl-Break", group="main")
THREAD START (obj=50002888, id = 200005, name="Thread-0", group="main")
THREAD START (obj=50002952, id = 200006, name="pool-1-thread-1", group="main")
........пропущено

TRACE 304360:
    java.io.InputStream.<init>(InputStream.java:28)
    java.io.FileInputStream.<init>(FileInputStream.java:128)
    java.net.SocketInputStream.<init>(SocketInputStream.java:44)
    java.net.PlainSocketImpl.getInputStream(PlainSocketImpl.java:401)
TRACE 304361:
    org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:96)
    org.apache.http.impl.io.SocketInputBuffer.<init>(SocketInputBuffer.java:106)
    org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:101)
    org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:179)
........пропущено

SITES BEGIN (ordered by live bytes) Fri Apr 13 16:55:08 2012
                     percent           live                alloc'ed      stack   class
 rank   self      accum    bytes      objs   bytes      objs  trace    name
     1   16.65% 16.65%   6648480  810     11294208  1376   304361 byte[]
     2   16.65% 33.30%   6648480  810     11294208  1376   304375 byte[]
     3   16.65% 49.96%   6648480  810     11294208  1376   304363 byte[]
     4    4.65%  54.61%   1858048 58064   2780672    86896 300436 java.util.concurrent.ConcurrentHashMap$Segment
     5    4.56%  59.17%   1820496 75854   2068728    86197 300859 java.util.HashMap$Entry
........пропущено
Поиск memory leak заключается в следующем: определяем по sites, объектов какого класса больше всего (здесь - много байтовых массивов); для него берём stack trace id, по которому ищем и смотрим саму цепочку вызовов.
Интересный и полезный параметр - глубина этой цепочки-трэйса. По дефолту - 4, однако этого может быть недостаточно для того, чтобы понять, где имеет утечка. Казалось бы, больше - лучше, но! Для одного и того же серверного приложения лог с depth=10 занял около 70 Мб, а с depth=20 - 850 Мб! Так что следует быть осторожным.
Ещё осторожным нужно быть по следующей причине: данные реально сохраняются в файл при остановке JVM. Соответственно, если JVM была остановлена каким-то нештатным образом, то данные профилирования не получим (конечно, можно использовать возможность получения данных в процессе работы, информация об этом содержится в заголовке предложенного листинга).
----------------------------------------------------------------------------------------------------

Подробнее про Java Virtual Machine Tool Interface (пришедшему на смену JMVPIprofiling) и JVMDI (debugging)) можно почитать JVM Tool Interface
Если совсем просто, то JVMTI - это штука, которая при старте JVM загружает заданную библиотеку. А эта библиотека - включённая в состав java (тот же hprof) или сторонняя (например, агент самостоятельного профайлера) - взаимодействует с JVM:

1) принимает события (например, старт или останов JVM, загрузка классов и пр.) и
2) спрашивает JVM (сколько сейчас живых объектов? в каком состоянии находится  конкретный поток?).
Т. о., JVMTI предоставляет интерфейс для получения всей этой информации, а уже как её обрабатывать - дело конкретной утилиты.
----------------------------------------------------------------------------------------------------

Для понимания причин утечки памяти может оказаться очень полезным дамп памяти.
Дамп можно снять прямо во время выполнения java процесса (по process id) утилитой JMAP (jmap - Memory Map). Дамп - по дефолту бинарный. ID процесса можно найти командой jps. Можно снять несколько дампов в разные периоды работы приложения и попытаться их сравнить.

----------------------------------------------------------------------------------------------------

Отличная функция - автоматическое сохранение дампа памяти при возникновении OutOfMemory. Её включение выполняется через настройку JVM:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs - путь относительно точки входа в приложение
При возникновении OOM в логе приложения появляется что-то вроде следующего:
jvm 1    | java.lang.OutOfMemoryError: Java heap space
jvm 1    | Dumping heap to d:\application\jboss\log\java_pid5432.hprof ...
jvm 1    | Heap dump file created [286948346 bytes in 10.989 secs]
и сохраняется файл. Смотреть его "глазами" тяжело и неудобно, поэтому лучше использовать средство автоматического анализа.
По поводу версий, в которых эта полезная опция присутствует:


The corresponding JVM option -XX:+HeapDumpOnOutOfMemoryError is supported not only in Java 6, but also in Sun Java 1.4.2_12 and newer and in Sun Java 5.0 update 7 and newer. Unfortunately, only in Java 6 it is accessible programmatically. The only way to enable it in pre-Java 6 JVM is to explicitly specify -XX:+HeapDumpOnOutOfMemoryError in the command line of the profiled application.
----------------------------------------------------------------------------------------------------
 
JHAT - Java Heap Analysis Tool
Встроенная в JDK утилита для автоматического анализа дампов. Запуск:
jhat -J-mx1024m java_pid5432.hprof
Утилита выполняет парсинг дампа, резолвит объекты, их ссылки, запускает http-сервер:
http://localhost:7000
Пример информации, выводимой в консоль при запуске утилиты:


Содержит список всех загруженных классов. Heap Histogram показывает статистику классов по Instance Count (количеству объектов) и Total Size (общему размеру). Также полезные данные:
# Show instance counts for all classes (including platform)
# Show instance counts for all classes (excluding platform)
Далее можно по каждому классу посмотреть список объектов, стэктрэйсы и пр.
Следует обратить внимание, что эта утилита для анализа дампа памяти и поиска причин OutOfMemory сама может сгенерировать OutOfMemory, когда пытается обработать большой и сложный дамп. И даже если парсинг выполнился без ошибок и сервер был запущен, открытие какой-нибудь вкладки может сильно нагрузить систему. Поэтому при старте утилиты лучше сразу выделять ей побольше памяти.
Что же можно открыть и проанализировать с помощью jhat? Утилита отлично работает с бинарными дампами, которые создают уже рассмотренные hprof, jmap и HeapDumpOnOutOfMemoryError!
----------------------------------------------------------------------------------------------------

Что ещё лучше, эти же бинарные дампы можно открыть и проанализировать и некоторыми полноценными профайлерами; как минимум, YourKit Java Profiler легко с ними работает.
----------------------------------------------------------------------------------------------------

Нужно понимать, что все рассмотренные утилиты имеют в совокупности меньший функционал, нежели какой-нибудь полноценный java профайлер. Вместе с тем, и они могут пригодиться. Флаг HeapDumpOnOutOfMemoryError, установленный для JVM однажды, не приносит никакого overhead'а, но позволит получить хоть какой-то дамп при неожиданном возникновении ошибки. HPROF с его умением подсчитывать количество и размеры объектов и выводом трейсов в принципе позволит сформировать предположение о месте утечки (особенно, если приобретение лицензии на "большой" профайлер невозможно).

Комментариев нет:

Отправить комментарий