- Home »

Снятие дампа потоков в Java: VisualVM, jstack, kill -3, jcmd
Если ты когда-нибудь чинил подвисший Java-сервер в 3 утра, пока на тебя давит продакт-менеджер в слаке, то знаешь — thread dump это твой спасательный круг. Это тот инструмент, который показывает что именно творится в недрах JVM, какие потоки заблокированы, где случились deadlock’и и почему твоё приложение решило превратиться в тыкву. Мы разберём все основные способы снятия дампов потоков: от удобного VisualVM до старого доброго kill -3, плюс современный jcmd. Каждый метод имеет свои плюсы и подводные камни, и я покажу когда использовать что.
Что такое thread dump и зачем он нужен
Thread dump — это снимок состояния всех потоков Java-приложения в конкретный момент времени. Представь себе фотографию того, что делает каждый поток: спит, ждёт лока, выполняет код или завис намертво. Это как взглянуть под капот работающего движка и увидеть какие поршни стучат, а какие застряли.
Основные сценарии использования:
- Высокая загрузка CPU — найти потоки, которые жрут процессор
- Deadlock’и — обнаружить взаимные блокировки
- Медленная работа — найти узкие места в коде
- OutOfMemoryError — понять что происходило перед падением
- Неотвечающее приложение — диагностировать полный freeze
VisualVM: графический интерфейс для ленивых
VisualVM — это GUI-тулза, которая идёт в комплекте с JDK (до Java 8) или скачивается отдельно. Удобно для разработки и тестирования, но на проде может быть избыточно.
Установка и подключение
Для локальных процессов VisualVM подключается автоматически. Для удалённых серверов нужно настроить JMX:
java -Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar your-application.jar
Внимание! Такая конфигурация небезопасна для продакшена. В реальности настрой аутентификацию и SSL.
Снятие дампа через VisualVM
Процесс элементарный:
- Запускаем VisualVM
- Выбираем нужный Java-процесс
- Переходим на вкладку “Threads”
- Жмём “Thread Dump”
Плюсы VisualVM:
- Наглядный интерфейс
- Встроенный анализатор дампов
- Можно наблюдать потоки в реальном времени
- Интегрированные инструменты профилирования
Минусы:
- Требует GUI
- Ресурсоёмкий
- Неудобен для автоматизации
- Может влиять на производительность
jstack: швейцарский нож для дампов
jstack — это консольная утилита, которая идёт в комплекте с JDK. Быстрая, надёжная, работает везде где есть Java.
Базовое использование
Сначала находим PID процесса:
# Через jps (поставляется с JDK)
jps -l
# Через ps
ps aux | grep java
# Через pgrep
pgrep -f "java.*your-app"
Снимаем дамп:
# Базовая команда
jstack PID
# Сохранение в файл
jstack PID > threaddump_$(date +%Y%m%d_%H%M%S).txt
# Принудительный дамп (если процесс не отвечает)
jstack -F PID
# Дамп с дополнительной информацией
jstack -l PID
Примеры практического использования
Скрипт для автоматического снятия серии дампов:
#!/bin/bash
PID=$1
COUNT=${2:-5}
INTERVAL=${3:-10}
if [ -z "$PID" ]; then
echo "Usage: $0 [count] [interval]"
exit 1
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DUMPDIR="dumps_${TIMESTAMP}"
mkdir -p "$DUMPDIR"
echo "Taking $COUNT thread dumps with ${INTERVAL}s interval..."
for i in $(seq 1 $COUNT); do
echo "Taking dump $i/$COUNT..."
jstack $PID > "$DUMPDIR/dump_${i}.txt"
if [ $i -lt $COUNT ]; then
sleep $INTERVAL
fi
done
echo "Dumps saved to $DUMPDIR"
Поиск проблемных потоков:
# Найти все заблокированные потоки
jstack PID | grep -A 5 -B 5 "BLOCKED"
# Найти deadlock'и
jstack PID | grep -A 10 "Found deadlock"
# Посчитать потоки по состояниям
jstack PID | grep "java.lang.Thread.State" | sort | uniq -c
kill -3: олдскульный способ
kill -3 (сигнал SIGQUIT) — это Unix-способ заставить JVM выплюнуть thread dump. Работает на всех POSIX-системах и не требует дополнительных инструментов.
Как это работает
# Отправляем сигнал SIGQUIT
kill -3 PID
# Альтернативный синтаксис
kill -QUIT PID
Дамп попадёт в stdout приложения. Если приложение логируется в файл, ищи дамп там:
# Обычно в логах приложения
tail -f /var/log/your-app/application.log
# Или в stderr
tail -f /var/log/your-app/error.log
# Для systemd сервисов
journalctl -u your-service -f
Автоматизация с kill -3
Скрипт для регулярного снятия дампов:
#!/bin/bash
PID=$1
LOGFILE="/var/log/your-app/application.log"
DUMPDIR="/tmp/dumps"
mkdir -p "$DUMPDIR"
# Запоминаем текущий размер лога
BEFORE=$(wc -l < "$LOGFILE")
# Отправляем сигнал
kill -3 "$PID"
# Ждём появления дампа
sleep 5
# Вычисляем новые строки
AFTER=$(wc -l < "$LOGFILE")
NEW_LINES=$((AFTER - BEFORE))
# Извлекаем дамп
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
tail -n "$NEW_LINES" "$LOGFILE" > "$DUMPDIR/dump_${TIMESTAMP}.txt"
echo "Thread dump saved to $DUMPDIR/dump_${TIMESTAMP}.txt"
Важно! kill -3 не убивает процесс, это именно сигнал для снятия дампа. Но в некоторых контейнерах (например, в Docker) может привести к завершению процесса.
jcmd: современная альтернатива
jcmd — это новый инструмент (начиная с Java 7), который объединяет функциональность нескольких утилит. Он более гибкий чем jstack и активно развивается.
Базовые команды
# Список Java-процессов
jcmd
# Список доступных команд для конкретного процесса
jcmd PID help
# Снятие thread dump
jcmd PID Thread.print
# Дамп с дополнительной информацией о локах
jcmd PID Thread.print -l
# Сохранение в файл
jcmd PID Thread.print > threaddump_$(date +%Y%m%d_%H%M%S).txt
Дополнительные возможности jcmd
jcmd умеет гораздо больше чем просто thread dump’ы:
# Информация о JVM
jcmd PID VM.info
# Heap dump
jcmd PID GC.run_finalization
jcmd PID VM.memory
# Свойства системы
jcmd PID VM.system_properties
# Статистика GC
jcmd PID GC.run
jcmd PID GC.class_histogram
# Информация о командной строке
jcmd PID VM.command_line
# Диагностические флаги
jcmd PID VM.flags
Сравнение методов
Метод | Скорость | Удобство | Автоматизация | Требования | Влияние на производительность |
---|---|---|---|---|---|
VisualVM | Средняя | Высокое | Низкая | GUI, JDK | Высокое |
jstack | Высокая | Среднее | Высокая | JDK | Низкое |
kill -3 | Высокая | Низкое | Средняя | Unix/Linux | Низкое |
jcmd | Высокая | Высокое | Высокая | JDK 7+ | Низкое |
Анализ thread dump’ов
Снять дамп — это половина дела. Важно уметь его читать. Вот основные паттерны:
Состояния потоков
- RUNNABLE — поток выполняется или готов к выполнению
- BLOCKED — поток заблокирован на synchronized блоке
- WAITING — поток ждёт notify/notifyAll
- TIMED_WAITING — поток ждёт с таймаутом
- TERMINATED — поток завершён
Поиск проблем
# Найти все BLOCKED потоки
grep -A 10 "BLOCKED" threaddump.txt
# Найти deadlock'и
grep -A 20 "Found deadlock" threaddump.txt
# Посчитать потоки по состояниям
grep "java.lang.Thread.State" threaddump.txt | sort | uniq -c | sort -nr
# Найти потоки с высоким стеком
awk '/^".*" #/ {thread=$0; count=0} /^\s+at/ {count++} /^$/ {if(count>50) print thread " - " count " frames"}' threaddump.txt
Инструменты для анализа
Ручной анализ дампов — это боль. Используй специализированные инструменты:
- Eclipse MAT — отличный анализатор с GUI
- FastThread.io — онлайн-анализатор (удобно, но не для чувствительных данных)
- IBM Thread and Monitor Dump Analyzer — мощный инструмент от IBM
- JProfiler — коммерческий профайлер с анализом дампов
Автоматизация и мониторинг
Для серьёзного мониторинга стоит автоматизировать процесс:
#!/bin/bash
# Скрипт для автоматического снятия дампов при высокой загрузке CPU
PID=$1
CPU_THRESHOLD=80
DUMP_DIR="/var/dumps"
MAX_DUMPS=10
mkdir -p "$DUMP_DIR"
while true; do
# Получаем загрузку CPU для процесса
CPU_USAGE=$(ps -p "$PID" -o pcpu= | tr -d ' ')
if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
echo "High CPU usage detected: ${CPU_USAGE}%"
# Проверяем количество дампов
DUMP_COUNT=$(ls -1 "$DUMP_DIR"/dump_*.txt 2>/dev/null | wc -l)
if [ "$DUMP_COUNT" -lt "$MAX_DUMPS" ]; then
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DUMPFILE="$DUMP_DIR/dump_${TIMESTAMP}.txt"
echo "Taking thread dump..."
jstack "$PID" > "$DUMPFILE"
echo "Thread dump saved: $DUMPFILE"
# Отправляем уведомление (например, в Slack)
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"High CPU usage detected on $(hostname): ${CPU_USAGE}%. Thread dump saved to $DUMPFILE\"}" \
"$SLACK_WEBHOOK_URL"
fi
fi
sleep 60
done
Интеграция с системами мониторинга
Для серверной инфраструктуры полезно интегрировать снятие дампов с системами мониторинга:
Prometheus + Grafana
Можно экспортировать метрики из thread dump’ов:
#!/bin/bash
# Экспорт метрик из thread dump в формате Prometheus
DUMP_FILE=$1
METRICS_FILE="/var/metrics/thread_metrics.prom"
# Подсчёт потоков по состояниям
grep "java.lang.Thread.State" "$DUMP_FILE" | \
awk '{print $2}' | \
sort | uniq -c | \
awk '{print "java_threads_" tolower($2) "_total " $1}' > "$METRICS_FILE"
# Общее количество потоков
TOTAL_THREADS=$(grep -c "java.lang.Thread.State" "$DUMP_FILE")
echo "java_threads_total $TOTAL_THREADS" >> "$METRICS_FILE"
# Timestamp
echo "# Generated at $(date)" >> "$METRICS_FILE"
Nagios/Icinga
Плагин для проверки состояния потоков:
#!/bin/bash
# Nagios plugin для проверки thread dump'ов
PID=$1
BLOCKED_THRESHOLD=10
CRITICAL_THRESHOLD=20
if [ -z "$PID" ]; then
echo "UNKNOWN - PID not specified"
exit 3
fi
# Снимаем дамп
DUMP=$(jstack "$PID" 2>/dev/null)
if [ $? -ne 0 ]; then
echo "CRITICAL - Cannot get thread dump"
exit 2
fi
# Проверяем deadlock'и
DEADLOCKS=$(echo "$DUMP" | grep -c "Found deadlock")
if [ "$DEADLOCKS" -gt 0 ]; then
echo "CRITICAL - $DEADLOCKS deadlock(s) detected"
exit 2
fi
# Подсчитываем заблокированные потоки
BLOCKED=$(echo "$DUMP" | grep -c "BLOCKED")
if [ "$BLOCKED" -ge "$CRITICAL_THRESHOLD" ]; then
echo "CRITICAL - $BLOCKED blocked threads"
exit 2
elif [ "$BLOCKED" -ge "$BLOCKED_THRESHOLD" ]; then
echo "WARNING - $BLOCKED blocked threads"
exit 1
else
echo "OK - $BLOCKED blocked threads"
exit 0
fi
Лучшие практики и подводные камни
Несколько важных моментов, которые стоит учитывать:
Производительность
- Снятие дампа может занимать несколько секунд на больших приложениях
- Во время снятия дампа JVM может “подвисать”
- Не снимай дампы слишком часто в продакшене
- Размер дампа может достигать сотен мегабайт
Безопасность
- Thread dump’ы могут содержать чувствительную информацию
- Настрой ротацию логов для дампов
- Ограничь доступ к файлам дампов
- Будь осторожен с JMX в продакшене
Типичные ошибки
- Один дамп ≠ диагноз — снимай серию дампов с интервалом
- Не игнорируй контекст — смотри на нагрузку, время, логи
- Deadlock’и не всегда очевидны — используй инструменты анализа
- BLOCKED != проблема — некоторые блокировки нормальны
Нестандартные случаи использования
Thread dump’ы можно использовать не только для дебага:
Профилирование производительности
Серия дампов может показать какие методы выполняются чаще всего:
#!/bin/bash
# "Бедный" профайлер на основе thread dump'ов
PID=$1
SAMPLES=60
INTERVAL=1
for i in $(seq 1 $SAMPLES); do
echo "=== Sample $i ===" >> profile.txt
jstack "$PID" | grep "^\s*at " >> profile.txt
sleep $INTERVAL
done
# Анализ самых частых методов
grep "^\s*at " profile.txt | sort | uniq -c | sort -nr | head -20
Обнаружение memory leak’ов
Анализ роста количества потоков может указать на утечки:
#!/bin/bash
# Мониторинг роста потоков
PID=$1
LOG_FILE="thread_growth.log"
while true; do
THREAD_COUNT=$(jstack "$PID" | grep "^\"" | wc -l)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "$TIMESTAMP $THREAD_COUNT" >> "$LOG_FILE"
# Анализ роста за последний час
HOUR_AGO=$(date -d '1 hour ago' '+%Y-%m-%d %H:%M:%S')
GROWTH=$(tail -n 3600 "$LOG_FILE" | awk -v start="$HOUR_AGO" '$1" "$2 > start {sum+=$3; count++} END {if(count>0) print ($3-sum/count)}')
if (( $(echo "$GROWTH > 50" | bc -l) )); then
echo "WARNING: Thread count increased by $GROWTH in last hour"
fi
sleep 60
done
Интеграция с CI/CD
Thread dump’ы можно использовать в автоматических тестах:
#!/bin/bash
# Тест на отсутствие deadlock'ов в CI
PID=$1
TEST_DURATION=300 # 5 минут
echo "Starting deadlock detection test..."
START_TIME=$(date +%s)
while [ $(($(date +%s) - START_TIME)) -lt $TEST_DURATION ]; do
DEADLOCKS=$(jstack "$PID" | grep -c "Found deadlock")
if [ "$DEADLOCKS" -gt 0 ]; then
echo "FAIL: Deadlock detected during test"
jstack "$PID" > "deadlock_dump_$(date +%Y%m%d_%H%M%S).txt"
exit 1
fi
sleep 10
done
echo "PASS: No deadlocks detected during $TEST_DURATION seconds"
exit 0
Заключение и рекомендации
Выбор метода снятия thread dump’ов зависит от конкретной ситуации:
- Для разработки и отладки — используй VisualVM, он наглядный и удобный
- Для продакшена — jcmd или jstack, они быстрые и надёжные
- Для экстренных ситуаций — kill -3, работает даже когда JVM еле дышит
- Для автоматизации — jcmd, у него самый богатый функционал
Основные принципы работы с thread dump’ами:
- Снимай серии дампов — один дамп показывает только момент времени
- Анализируй в контексте — учитывай нагрузку, время, логи приложения
- Используй инструменты — ручной анализ больших дампов неэффективен
- Автоматизируй — настрой автоматическое снятие дампов при проблемах
- Думай о безопасности — дампы могут содержать чувствительную информацию
Если работаешь с высоконагруженными системами, обязательно настрой автоматическое снятие дампов при превышении пороговых значений CPU или при обнаружении других аномалий. Это сэкономит кучу времени при расследовании инцидентов.
Для серьёзной работы с Java-приложениями рекомендую арендовать VPS или выделенный сервер с достаточными ресурсами. Thread dump’ы больших приложений могут занимать значительное место на диске, плюс нужна память для инструментов анализа.
Помни: thread dump — это не панацея, а один из инструментов диагностики. Используй его в связке с профайлерами, мониторингом и логированием для получения полной картины происходящего.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.