Home » Снятие дампа потоков в Java: VisualVM, jstack, kill -3, jcmd
Снятие дампа потоков в Java: VisualVM, jstack, kill -3, jcmd

Снятие дампа потоков в 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

Процесс элементарный:

  1. Запускаем VisualVM
  2. Выбираем нужный Java-процесс
  3. Переходим на вкладку “Threads”
  4. Жмём “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’ами:

  1. Снимай серии дампов — один дамп показывает только момент времени
  2. Анализируй в контексте — учитывай нагрузку, время, логи приложения
  3. Используй инструменты — ручной анализ больших дампов неэффективен
  4. Автоматизируй — настрой автоматическое снятие дампов при проблемах
  5. Думай о безопасности — дампы могут содержать чувствительную информацию

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

Для серьёзной работы с Java-приложениями рекомендую арендовать VPS или выделенный сервер с достаточными ресурсами. Thread dump’ы больших приложений могут занимать значительное место на диске, плюс нужна память для инструментов анализа.

Помни: thread dump — это не панацея, а один из инструментов диагностики. Используй его в связке с профайлерами, мониторингом и логированием для получения полной картины происходящего.


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

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

Leave a reply

Your email address will not be published. Required fields are marked