Как получить heap dump
Самый надежный и распространенный подход — настроить виртуальную машину так, чтобы она сама сохраняла снимок памяти при исчерпании ресурсов:
🟢 Junior Level
Heap Dump (дамп кучи) можно получить несколькими способами: автоматически при сбое приложения, вручную через консоль или с помощью графических инструментов разработки.
1. Автоматически при OutOfMemoryError (Production Must-Have)
Самый надежный и распространенный подход — настроить виртуальную машину так, чтобы она сама сохраняла снимок памяти при исчерпании ресурсов:
java -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/dumps/app_oom.hprof \
-jar app.jar
2. Вручную через консольную утилиту jcmd
Если приложение работает медленно или есть подозрение на утечку, дамп можно снять на лету:
# 1. Узнать PID процесса
jps
# или: ps aux | grep java
# 2. Снять дамп памяти
jcmd <PID> GC.heap_dump /tmp/heap_dump.hprof
3. Через графический интерфейс (VisualVM / IntelliJ IDEA)
- В VisualVM: найти процесс в списке слева, нажать правой кнопкой мыши $\to$ Heap Dump.
- В IntelliJ IDEA Ultimate: открыть вкладку Profiler $\to$ выбрать запущенный процесс $\to$ Capture Memory Snapshot.
🟡 Middle Level
1. Продвинутые параметры снятия через jcmd
Утилита jcmd поддерживает параметры сжатия и фильтрации:
# Сжатие на лету архиватором gzip (Java 11u12+ и Java 17+)
# -gz=1 (минимальное сжатие, минимальная нагрузка на CPU)
# -gz=9 (максимальное сжатие, меньший размер файла)
jcmd <PID> GC.heap_dump -gz=1 /tmp/dump.hprof.gz
# Снятие ТОЛЬКО ЖИВЫХ объектов (инициирует предварительный Full GC)
jcmd <PID> GC.heap_dump -all=false /tmp/live_dump.hprof
[!NOTE] Файл дампа кучи размером 32 ГБ при флаге
-gz=1сжимается до 6–8 ГБ. Это экономит место на диске и в разы сокращает время скачивания дампа с удаленного сервера.
2. Программное снятие из Java-кода
HotSpot предоставляет официальный JMX-бин HotSpotDiagnosticMXBean, позволяющий запросить дамп непосредственно из бизнес-кода:
import com.sun.management.HotSpotDiagnosticMXBean;
import java.io.IOException;
import java.lang.management.ManagementFactory;
public class DiagnosticService {
public static void dumpHeap(String filePath, boolean liveOnly) throws IOException {
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
ManagementFactory.getPlatformMBeanServer(),
"com.sun.management:type=HotSpotDiagnostic",
HotSpotDiagnosticMXBean.class
);
// liveOnly=true вызывает предварительный Full GC
mxBean.dumpHeap(filePath, liveOnly);
}
}
3. Снятие через Spring Boot Actuator
В микросервисах на Spring Boot доступен встроенный эндпоинт Actuator:
# Скачивание дампа кучи через HTTP GET
curl -O http://localhost:8080/actuator/heapdump
- Под капотом Actuator обращается к
HotSpotDiagnosticMXBean, сохраняет временный файл и передает его в виде HTTP-ответа (application/octet-stream). - ⚠️ Риск безопасности: данный эндпоинт обязан быть защищен авторизацией (роль ADMIN) или закрыт от внешней сети, иначе злоумышленник сможет скачать всю память сервиса с токенами и паролями.
🔴 Senior Level
1. Механика Attach API и ловушки прав доступа в контейнерах
Команда jcmd взаимодействует с целевой виртуальной машиной через HotSpot Attach API:
- Клиент
jcmdсоздает сигнальный файл в каталоге временных файлов (/tmp/.java_pid<PID>); - Отправляет процессу JVM сигнал
SIGQUIT(3); - Поток
Signal Dispatcherвнутри JVM активирует потокAttach Listener, который открывает Unix Domain Socket (/tmp/.java_pid<PID>); jcmdпередает командуGC.heap_dumpчерез сокет.
Типичные сбои в Docker / Kubernetes:
- Ошибка
Unable to open socket file: В production контейнеры запускаются под непривилегированным пользователем (например,appuserс UID 10001). Если войти в контейнер подrootи выполнитьjcmd 1 GC.heap_dump ..., команда завершится с ошибкой прав доступа. Решение: выполнять команду от имени того же пользователя, под которым работает процесс:su -s /bin/sh appuser -c "jcmd 1 GC.heap_dump -gz=1 /tmp/dump.hprof.gz" - Изоляция каталога
/tmp: Если/tmpсмонтирован с опциейnoexecили JVM запущена с флагом-XX:+DisableAttachMechanism,jcmdне сможет подключиться.
2. Снятие дампа с пода Kubernetes под боевой нагрузкой
Пошаговый сценарий выгрузки памяти без падения сервиса:
# Шаг 1: Изолировать под от входящего трафика (удалить лейбл селектора сервиса)
kubectl label pod order-service-7f8d-xkz pod-under-diag=true --overwrite
kubectl label pod order-service-7f8d-xkz app-
# Шаг 2: Выполнить команду снятия дампа со сжатием на примонтированный том
kubectl exec order-service-7f8d-xkz -- jcmd 1 GC.heap_dump -gz=1 /dumps/diag.hprof.gz
# Шаг 3: Скопировать дамп на локальную станцию инженера
kubectl cp order-service-7f8d-xkz:/dumps/diag.hprof.gz ./diag.hprof.gz
# Шаг 4: Удалить временный файл дампа из пода
kubectl exec order-service-7f8d-xkz -- rm /dumps/diag.hprof.gz
# Шаг 5: Перезапустить или удалить под, чтобы восстановить балансировку
kubectl delete pod order-service-7f8d-xkz
3. Снятие дампа через GNU Debugger (gcore)
Если JVM полностью зависла в мертвом цикле (Thread deadlock, uninterruptible sleep, повреждение структур данных) и не отвечает на команды Attach API (jcmd висит бесконечно), единственным способом снять память процесса является генерация системного core dump через ядро Linux:
# Снятие системного дампа памяти ядра Linux без участия JVM
gcore -o /dumps/core.dump <PID>
# Конвертация системного core dump в Java HPROF с помощью утилиты jhsdb
jhsdb jmap --exe $JAVA_HOME/bin/java --core /dumps/core.dump.<PID> --binaryheap --dumpfile /dumps/converted.hprof
Этот подход работает на уровне ядра ОС и позволяет исследовать даже намертво зависшие или упавшие процессы.
🎯 Шпаргалка для интервью
30-секундный ответ для собеседования
«Получить Heap Dump можно автоматически при сбое через параметры виртуальной машины
-XX:+HeapDumpOnOutOfMemoryErrorи-XX:HeapDumpPath, либо вручную через консольную утилитуjcmd <PID> GC.heap_dump [-gz=1] <path>. В современных версиях Java рекомендуется использовать флаг-gz=1для сжатия дампа на лету, что экономит до 75% места. В коде дамп можно снять черезHotSpotDiagnosticMXBeanили эндпоинт Spring Boot Actuator/actuator/heapdump. В контейнерах важно помнить о правах пользователя (UID):jcmdдолжен запускаться из-под того же пользователя, что и процесс Java».
Вопросы повышенной сложности с ответами
-
Почему
jcmd <PID> GC.heap_dumpвнутри Docker-контейнера может выдать ошибкуUnable to open socket file? Ответ: Attach API JVM опирается на создание Unix domain socket в каталоге/tmp/.java_pid<PID>. Если оператор заходит в контейнер черезkubectl execпод пользователемroot, а Java-процесс запущен под непривилегированным пользователем (например,appuser), ядро ОС запретит доступ к сокету из-за несовпадения прав UID/GID. Для решения командыjcmdнеобходимо выполнять от имени того же пользователя, под которым работает JVM (su -s /bin/sh appuser -c "jcmd ..."). - Чем опасен вызов
/actuator/heapdumpна боевом Spring Boot сервисе? Ответ: Эндпоинт запускает синхронное снятие дампа памяти черезHotSpotDiagnosticMXBean. Это:- Вводит JVM в Stop-The-World паузу на всё время формирования дампа;
- Забивает сеть и процессор передачей десятков гигабайт потока данных;
- Создает риск отказа в обслуживании (DoS);
- Несет критическую угрозу безопасности: дамп содержит открытые токены, ключи шифрования и персональные данные пользователей.
-
Что произойдет, если во время выполнения
GC.heap_dumpна сервере закончится свободное место на диске? Ответ: Системный вызов записи на диск вернет ошибкуNo space left on device(ENOSPC). JVM перехватит эту ошибку ввода-вывода, прервет операцию сохранения дампа, выйдет из точки останова Safepoint и продолжит работу приложения. При этом на диске останется незавершенный поврежденный.hprofфайл, который не сможет быть открыт стандартными анализаторами без предварительного ручного восстановления. - В чём разница между снятием дампа через
jcmdи снятием черезgcore + jhsdb? Ответ:jcmdработает через механизм Attach API самой виртуальной машины HotSpot и требует, чтобы JVM была жива и способна остановить потоки в точке Safepoint.gcore— это утилита ОС Linux, снимающая полный дамп адресного пространства процесса через ядро операционной системы независимо от состояния JVM.gcoreприменяется в аварийных ситуациях, когда JVM наглухо зависла на нативном уровне или в непрерываемом системном вызове иjcmdне отвечает.
Типичные ошибки и Red Flags
- ❌ Рекомендовать устаревшую утилиту
jmapвместоjcmd. (Red Flag: устаревшие знания;jcmdявляется рекомендованным стандартом HotSpot). - ❌ Оставлять незащищенным эндпоинт
/actuator/heapdumpв публичном контуре. (Критическая уязвимость безопасности: утечка учетных записей и пользовательских данных). - ❌ Снимать дамп в контейнере в корневую файловую систему (
/tmp), не проверив дисковые квоты. (Риск положить всю ноду Kubernetes из-за DiskPressure).