← Назад к вопросам

Что делать если завис сервис

2.0 Middle🔥 161 комментариев
#JVM и управление памятью#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что делать если завис сервис - диагностика и восстановление

Это сценарий, с которым столкнется практически каждый Java разработчик в production. Важно иметь четкий алгоритм действий для быстрой диагностики и восстановления сервиса.

Первые действия (первые 30 секунд)

1. Подтвердить факт зависания

Сервис может казаться зависшим, но на самом деле просто медленным.

# Проверить доступность сервиса
curl -v http://localhost:8080/health

# Если ответа нет - сервис действительно зависший
# Если ответ медленный - проблема производительности (другой случай)

2. Посмотреть логи

# Последние логи
tail -100 /var/log/app/application.log

# В контейнере
docker logs -f myapp | tail -100

# В kubernetes
kubectl logs -f deployment/myapp --tail=100

Ищи:

  • Stack traces (исключения)
  • Deadlock messages
  • Out of memory errors
  • Network timeouts

3. Проверить ресурсы сервера

# CPU и память
top

# Более детально для Java процесса
jps -l  # Найти Java процесс

# Смотри:
# - CPU usage > 100% (использует все ядра)
# - Memory usage близко к лимиту
# - Процесс зависает (STAT = D или T)

Второй этап: Сбор диагностической информации (2-5 минут)

1. Thread dump

Получить снимок всех потоков - критически важно для понимания причины зависания.

# Найти PID Java процесса
PID=$(pgrep -f myapp.jar)

# Получить thread dump
jstack $PID > thread_dump.txt

# Или с jcmd
jcmd $PID Thread.print > thread_dump.txt

Анализ thread dump:

# Ищи паттерны:

1. Deadlock признаки:
   "Thread-1" prio=10 tid=0x00007f...
   java.lang.Thread.State: BLOCKED (on object monitor)
   at java.util.HashMap.get(HashMap.java:...)
   - waiting to lock <...>
   at App.method(...)

2. Infinite loop:
   at myapp.HotSpotMethod.doSomething(MyClass.java:123)
   at myapp.HotSpotMethod.doSomething(MyClass.java:120)
   at myapp.HotSpotMethod.doSomething(MyClass.java:120)  # Повтор!

3. Ожидание ввода-вывода:
   java.lang.Thread.State: WAITING (parking)
   at sun.misc.Unsafe.park(Native Method)
   at java.util.concurrent.locks.LockSupport.park(...)

4. Все потоки заняты:
   Все потоки в RUNNABLE состоянии - может быть infinite loop

2. Heap dump

Если заподозреваешь memory leak или OOM.

PID=$(pgrep -f myapp.jar)

# Создать heap dump
jcmd $PID GC.heap_dump filename=heap_dump.hprof

# Анализировать позже в Eclipse MAT

3. Проверить соединения

# Сколько соединений открыто
lsof -p $PID | grep TCP

# Сколько потоков создано
ps -p $PID -L | wc -l

# Ожидающие соединения
netstat -an | grep ESTABLISHED | wc -l

Третий этап: Определение причины

Причина 1: Deadlock

Топический признак в thread dump:
Found one Java-level deadlock:
==============================
"Thread-1":
  waiting to lock monitor 0x00007f... (a java.lang.Object)
  at MyClass.methodA(MyClass.java:50)
  - locked <0x00007f...> (a java.lang.Object)
  at MyClass.methodB(MyClass.java:30)

"Thread-2":
  waiting to lock monitor 0x00007f... (a java.lang.Object)
  at MyClass.methodB(MyClass.java:30)
  - locked <0x00007f...> (a java.lang.Object)
  at MyClass.methodA(MyClass.java:50)

Решение: Перезагрузка (единственный выход)

kill -9 $PID
servicectl restart myapp
# или
docker restart myapp

Причина 2: Memory Leak / OOM

Признаки:

  • Memory usage постоянно растет
  • java.lang.OutOfMemoryError в логах
  • GC overhead limit exceeded
# Проверить текущую память
jcmd $PID VM.memory_usage

# Вывод:
# Java Heap (committed): 2G
# Java Heap (used):      1.9G  # Близко к лимиту

Быстрое решение:

# Увеличить heap размер
export JVM_OPTS="-Xmx4g -Xms4g"
kill -9 $PID
# Перезагрузить с новыми параметрами

Долгосрочное решение:

  • Найти memory leak в коде (часто незавершенные connection pools, listener'ы)
  • Использовать профайлер (YourKit, JProfiler)

Причина 3: Бесконечный цикл / Высокое CPU

Признаки: CPU > 90%, сервис отвечает медленно
# Найти самый "горячий" поток
jstack $PID | grep -A 20 "tid" | head -50

# Или с async-profiler
/opt/async-profiler/asprof -d 10 -f flamegraph.html $PID

Решение:

  • Оптимизировать алгоритм (обычно N² вместо N log N)
  • Добавить кэширование
  • Параллелизм

Причина 4: Network Timeout / Блокирующие I/O

Признаки в thread dump:
java.lang.Thread.State: WAITING (on object monitor)
at java.net.SocketInputStream.read0(Native Method)
at com.example.MyDatabaseClient.query(MyDatabaseClient.java:100)

Решение:

  • Добавить timeout на external API calls
  • Использовать non-blocking I/O (async, CompletableFuture)
  • Circuit breaker pattern
@Service
public class DatabaseClient {
  
  // Плохо - может зависнуть
  public Data queryDatabase() {
    return database.query(); // Бесконечное ожидание
  }
  
  // Хорошо - с timeout
  public Data queryDatabaseWithTimeout() {
    return database.query();
  }
}

Причина 5: Resource Exhaustion

# Слишком много потоков
ps -p $PID -L | wc -l  # > 10000 threads

# Слишком много соединений
lsof -p $PID | grep TCP | wc -l  # > 10000

Решение:

  • Ограничить thread pool размер
  • Ограничить connection pool
  • Закрыть незакрытые соединения

Пошаговый алгоритм восстановления

Вариант 1: Graceful restart

#!/bin/bash

PID=$(pgrep -f myapp.jar)

# 1. Остановить прием новых запросов
# Используй load balancer или proxy
# Например, с nginx
ngx_signal reload  # Перезагружает конфиг,停止 отправку на этот сервер

# 2. Дать время для завершения текущих операций
sleep 30

# 3. Закрыть сервис
kill -TERM $PID  # Graceful shutdown
sleep 10

# 4. Если не завершилось - force kill
kill -9 $PID

# 5. Перезагрузить
servicectl restart myapp

# 6. Проверить здоровье
sleep 5
curl http://localhost:8080/health

# 7. Вернуть в load balancer
# nginx auto-detect или manual

Вариант 2: Kubernetes

# 1. Удалить pod (он пересоздастся)
kubectl delete pod myapp-xyz-123

# 2. Или перезагрузить deployment
kubectl rollout restart deployment/myapp

# 3. Следить за progress
kubectl rollout status deployment/myapp

Вариант 3: Docker

# 1. Graceful stop
docker stop myapp

# 2. Restart
docker start myapp

# 3. Или просто перезагрузить
docker restart myapp

Профилактика (предотвращение зависаний)

1. Health checks

@RestController
@RequestMapping("/health")
public class HealthController {
  
  @GetMapping
  public ResponseEntity<Health> health() {
    // Проверить критические компоненты
    if (isDatabaseConnected() && 
        isThreadPoolHealthy() && 
        isMemoryOk()) {
      return ResponseEntity.ok(new Health("UP"));
    }
    return ResponseEntity.status(503).body(new Health("DOWN"));
  }
  
  private boolean isThreadPoolHealthy() {
    // Проверить thread pool
    ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool;
    int activeCount = executor.getActiveCount();
    int queueSize = executor.getQueue().size();
    
    // Если очередь растет - это проблема
    return queueSize < 1000;
  }
}

2. Monitoring и Alerting

# Prometheus metrics
# thread_count > 500 -> Alert
# memory_usage > 80% -> Alert
# response_time > 5s -> Alert

# Datadog / New Relic / AppDynamics setup

3. Resource Limits

# Docker / Kubernetes
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: myapp
    image: myapp:latest
    resources:
      limits:
        memory: "2Gi"
        cpu: "1000m"
      requests:
        memory: "1Gi"
        cpu: "500m"
    env:
    - name: JVM_OPTS
      value: "-Xmx1500m -Xms1500m"

4. Timeout везде

// Spring REST Client
@Bean
public RestTemplate restTemplate() {
  HttpComponentsClientHttpRequestFactory factory = 
    new HttpComponentsClientHttpRequestFactory();
  factory.setConnectTimeout(5000);  // 5 секунд
  factory.setReadTimeout(10000);    // 10 секунд
  return new RestTemplate(factory);
}

// Database
@Bean
public DataSource dataSource() {
  HikariConfig config = new HikariConfig();
  config.setConnectionTimeout(10000);
  config.setIdleTimeout(600000);
  config.setMaxLifetime(1800000);
  return new HikariDataSource(config);
}

5. Circuit Breaker

@Service
public class ExternalApiService {
  
  @CircuitBreaker(name = "externalApi", fallbackMethod = "fallback")
  public String callExternalApi() {
    return externalApi.getData();
  }
  
  public String fallback(Exception e) {
    log.error("Circuit breaker triggered", e);
    return "Fallback data";  // Возврат кэшированных данных
  }
}

Чеклист при зависании

  • Подтвердить что сервис действительно зависший
  • Собрать thread dump
  • Собрать heap dump (если подозреваешь memory leak)
  • Проверить логи на ошибки
  • Проверить ресурсы (CPU, memory)
  • Определить причину (deadlock, OOM, loop, timeout)
  • Применить краткосрочное решение (restart)
  • Назначить анализ (code review, performance test)
  • Внедрить долгосрочное решение

Быстрые действия спасают production, но анализ и профилактика спасают репутацию!

Что делать если завис сервис | PrepBro