Перейти к основному содержимому

JVM в проде — jcmd, дамп памяти и JFR

Разработчику Архитектору

JVM в проде — jcmd, дамп памяти и JFR

Теория GC и потоков — в JVM, память и потоки. Здесь — практика: какие команды ввести, когда приложение тормозит, «ест» память или упало с OutOfMemoryError.

Инструменты входят в JDK (каталог bin рядом с java), не в урезанную JRE: jcmd, jmap, jfr.

Словарь перед командами

ТерминОбъяснение
JVMПроцесс, в котором крутится ваш .jar (Spring Boot, Tomcat внутри)
PIDНомер процесса в ОС; подставляете вместо <pid> в командах
Heap (куча)Память для объектов (new, коллекции); OOM Java heap space — про неё
MetaspaceПамять под метаданные классов; отдельный OOM Metaspace
Heap dumpСнимок всех объектов в куче в файл .hprof для анализа
Thread dumpСнимок стеков всех потоков в момент времени — ищут deadlock и «горячий» код
JFRJava Flight Recorder — короткая запись профиля: CPU, GC, аллокации
GCСборщик мусора освобождает объекты без ссылок

Когда что делать (дерево решений):

Приложение «висит» или CPU 100% → Thread.print (jcmd)
OutOfMemoryError / память растёт → heap dump + Eclipse MAT / IDEA
Нужно «где тормозит метод» → JFR 60 сек + JDK Mission Control

Когда открывать эту статью

СимптомПервый шаг
«Висит», CPU 100%jcmd <pid> Thread.print
OutOfMemoryError: Java heap spaceheap dump + анализ
Нужно понять, куда ушло времякороткая запись JFR

1. Найти PID процесса

Linux / macOS:

jcmd
# или
ps aux | grep java

Windows (PowerShell):

jcmd
# или диспетчер задач → подробности → java.exe

Запомните число PID — подставляйте вместо <pid> ниже. Если процессов Java несколько, смотрите колонку с main-классом или аргументами (com.example.demo.DemoApplication).

Проверка JDK: java -version и jcmd -h должны работать из той же установки, которой запущено приложение.


2. Снимок потоков (зависания, высокий CPU)

jcmd <pid> Thread.print > threads.txt

Откройте threads.txt: ищите повторяющийся стек в состоянии RUNNABLE — часто там цикл или блокировка.

Разбор файла threads.txt:

Что видитеКак читать
"main" #1 prio=5 os_prio=0 tid=...Имя потока и номер
java.lang.Thread.State: RUNNABLEПоток выполняется или ждёт CPU
BLOCKED / WAITINGЖдёт lock или wait()
at com.example.service.Foo.bar(Foo.java:42)Стек: сверху вниз — от текущего кадра к main
Один и тот же at ... десятки раз в RUNNABLEЧастый признак бесконечного цикла или горячего метода

Deadlock: в конце дампа JVM иногда печатает Found one Java-level deadlock — два потока держат lock друг друга.


3. Heap dump (утечки памяти, OOM)

jcmd <pid> GC.heap_dump heap.hprof

Альтернатива (устаревающая, но встречается):

jmap -dump:live,format=b,file=heap.hprof <pid>

Файл heap.hprof откройте в Eclipse MAT, VisualVM или IntelliJ IDEA (Analyze Heap Dump).

На что смотреть в анализаторе:

  • Dominator tree — кто держит больше всего памяти;
  • Leak Suspects — эвристики утечек;
  • сравнение двух дампов «до/после» нагрузки.

:::tip OOM в логах Запустите JVM с -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dumps — дамп появится автоматически при падении. :::


4. Краткая запись JFR (профилирование)

Java Flight Recorder — встроенный профайлер Oracle JDK / Temurin 11+.

Запись 60 секунд:

jcmd <pid> JFR.start name=debug settings=profile duration=60s filename=recording.jfr
# подождать минуту
jcmd <pid> JFR.dump name=debug filename=recording.jfr
jcmd <pid> JFR.stop name=debug

Откройте recording.jfr в JDK Mission Control (JMC) или в IDEA (Analyze JFR Snapshot).

Разбор: в JMC смотрите Method Profiling, GC, Memory — где CPU и аллокации.


5. Минимальный чек-лист инцидента

  1. Зафиксировать время и версию (java -version).
  2. jcmd <pid> VM.flags — флаги JVM.
  3. jcmd <pid> GC.heap_info — размер кучи.
  4. Thread.print при подозрении на deadlock/CPU.
  5. Heap dump или JFR — по типу проблемы.
  6. Перезапуск только после сбора артефактов (если политика позволяет).

Подробнее о флагах и GC — 23.md, справочник — 3.md.


Пример — учебная утечка

import java.util.ArrayList;
import java.util.List;

public class LeakDemo {
static final List<byte[]> LEAK = new ArrayList<>();

public static void main(String[] args) throws InterruptedException {
while (true) {
LEAK.add(new byte[1024 * 1024]); // 1 MiB
Thread.sleep(100);
}
}
}

Запуск с дампом при OOM:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. LeakDemo

В MAT список byte[] и статическая LEAK будут доминировать — хорошая тренировка чтения дампа.


Частые ошибки

ОшибкаЧто делать
jcmd не найденВ PATH должен быть каталог bin JDK, не JRE
Отказ в доступе к PIDПрава пользователя / sudo (осторожно в проде)
Огромный .hprofДамп на диск с местом; live уменьшает размер
Путать heap и metaspaceOOM бывает разный — читайте текст исключения

Что попробовать

  1. Снять Thread.print для своего Spring Boot под нагрузкой ab или curl в цикле.
  2. Включить HeapDumpOnOutOfMemoryError в dev-профиле.
  3. Вернуться к 23.md и сопоставить GC-логи с JFR.

Дальше

JVM, память и потоки · Отладка в IDEA · Testcontainers


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).