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

С какими инструментами профилирования работал

1.7 Middle🔥 181 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

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

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

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

С какими инструментами профилирования работал

Рассмотрю инструменты, с которыми я работал для оптимизации performance приложений Java.

1. JProfiler (самый полнофункциональный)

Где: Корпоративный проект с высоконагруженной системой (10k RPS)

Функции:

  • CPU profiling (горячие методы)
  • Memory profiling (утечки памяти, heap dump анализ)
  • Thread profiling (deadlocks, contentions)
  • JDBC profiling (медленные запросы)

Как использовал:

// Проблема: Payment processing занимает 5 секунд вместо 100ms

// Запустил JProfiler, профилировал обработку платежа
// Результаты:
// - PaymentValidator.validate() → 3500ms (!!)
// - Database query → 1500ms
// - Stripe API call → 1000ms

// Анализ проблемы:
// Оказалось, что validator делал 10000 database queries
// вместо одного (N+1 проблема)

// Решение:
@Service
public class PaymentValidator {
    @Autowired
    private PaymentRepository repo;
    
    // ❌ Было так (медленно)
    public List<Payment> validatePayments(List<UUID> ids) {
        return ids.stream()
            .map(id -> repo.findById(id))  // N queries!
            .map(p -> p.validate())
            .collect(toList());
    }
    
    // ✅ После оптимизации
    public List<Payment> validatePayments(List<UUID> ids) {
        List<Payment> payments = repo.findAllById(ids);  // 1 query
        return payments.stream()
            .map(p -> p.validate())
            .collect(toList());
    }
}

// Результат: 3500ms → 250ms

Плюсы:

  • Детальная информация
  • Красивые графики и фламграфы
  • Интеграция с IDE (IntelliJ IDEA)

Минусы:

  • Платный ($500+)
  • Требует запуска приложения в специальном режиме
  • Overhead на производительность (10-20%)

2. YourKit

Где: Микросервис обработки платежей (нужна стабильность)

Функции:

  • Профилирование CPU и памяти
  • Анализ memory leaks
  • Профилирование в production (с низким overhead)

Пример использования:

// Проблема: Память растёт на 100MB в день
// Вероятно, утечка памяти

// Решение: Сделал heap dump в YourKit
// Анализ показал:
// - OrderCache хранит 1M объектов
// - Объекты никогда не удаляются (память растёт)

// Было:
@Service
public class OrderCache {
    private Map<UUID, Order> cache = new HashMap<>();
    
    public void cache(Order order) {
        cache.put(order.getId(), order);
        // Забыл удалять старые!
    }
}

// Стало:
@Service
public class OrderCache {
    private Map<UUID, Order> cache = new ConcurrentHashMap<>();
    
    @Scheduled(fixedRate = 300000)  // Каждые 5 минут
    public void evictOldEntries() {
        cache.entrySet().stream()
            .filter(e -> e.getValue().getCreatedAt()
                .isBefore(LocalDateTime.now().minusHours(1)))
            .map(Map.Entry::getKey)
            .forEach(cache::remove);
    }
}

// Или используй Guava Cache с expiration:
@Bean
public LoadingCache<UUID, Order> orderCache() {
    return CacheBuilder.newBuilder()
        .expireAfterWrite(1, TimeUnit.HOURS)
        .build(new CacheLoader<UUID, Order>() {
            @Override
            public Order load(UUID key) {
                return orderService.findById(key);
            }
        });
}

Плюсы:

  • Low overhead (1-5% для production profiling)
  • Отличный анализ памяти
  • Удалённое профилирование

Минусы:

  • Платный ($500+)
  • Сложнее в настройке

3. Async Profiler (мой фаворит для production)

Где: Production микросервисы (не хочу перезапускать)

Плюсы:

  • Абсолютно бесплатный
  • Очень низкий overhead (< 1%)
  • Не требует restart приложения
  • Работает через jstack

Использование:

# Установка
wget https://github.com/jvm-profiling-tools/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz
tar xzf async-profiler-3.0-linux-x64.tar.gz

# Профилирование на 30 секунд
./profiler.sh -d 30 -e cpu -f flamegraph.html <PID>

# Результат: фламграф в flamegraph.html
# Показывает, где тратится CPU (иерархия)

Пример анализа:

Выпуск payment processing требует 20% больше CPU

Шаги:
1. Запустил: ./profiler.sh -d 30 -e cpu -f result.html $(pgrep -f payment-service)

2. Открыл result.html

3. Фламграф показал:
   PaymentService.process() 
     ├─ 40% → StripeAPI.charge()  // Network I/O
     ├─ 35% → EmailService.send() // Network I/O
     ├─ 20% → ValidationService.validate()
     └─ 5%  → Logging

4. Вывод: проблема не в коде, а в network I/O
   Решение: добавить асинхронность

4. JVM Monitor (через VisualVM)

Где: Разработка локально

Что мониторит:

  • Heap usage (память)
  • GC frequency
  • Thread count
  • CPU usage

Как использовать:

# VisualVM встроен в JDK
jvisualvm

# Подключиться к процессу
# Applications → выбрать процесс

# Monitor tab → видишь:
# - Heap: 256MB / 512MB (растёт/не растёт)
# - Threads: 42 потоков
# - GC: Major GC каждые 10 секунд

Пример проблемы:

Замечу: GC паузы каждые 5 секунд на 2 секунды

Диагноз: Heap слишком мал для нагрузки

Решение:
java -Xmx2g -Xms2g MyApp

Результат: GC паузы редки, приложение responsive

5. Spring Boot Actuator + Micrometer

Где: Микросервисы с мониторингом (Spring Boot)

Метрики, которые я собираю:

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: metrics,health,prometheus
  metrics:
    export:
      prometheus:
        enabled: true

Использование:

@Service
public class PaymentService {
    private final MeterRegistry meterRegistry;
    private final Counter paymentCounter;
    private final Timer paymentTimer;
    
    public PaymentService(MeterRegistry registry) {
        this.meterRegistry = registry;
        this.paymentCounter = Counter.builder("payments.processed")
            .description("Total payments processed")
            .register(registry);
        this.paymentTimer = Timer.builder("payment.duration")
            .description("Payment processing time")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
    
    public PaymentResult process(Payment payment) {
        return paymentTimer.recordCallable(() -> {
            PaymentResult result = chargeCard(payment);
            paymentCounter.increment();
            return result;
        });
    }
}

// Метрики доступны по:
// http://localhost:8080/actuator/metrics/payment.duration
// http://localhost:8080/actuator/prometheus

В Grafana это выглядит так:

Дашборд "Payment Service":
├─ Payments/sec (RPS)
├─ P95 latency (ms)
├─ Error rate (%)
├─ Heap usage
├─ CPU usage
└─ GC pause duration

6. JMeter для load testing

Где: Тестирование performance перед release

Пример тестирования endpoint'а:

План теста:
1. 100 virtual users
2. Ramp-up period: 10 seconds
3. Loop: 10 итераций
4. Request: POST /api/payments

Результаты:
- Throughput: 950 RPS
- Avg response: 105ms
- P95: 250ms
- P99: 500ms
- Error rate: 0.1%

HTTP запрос в JMeter:

POST http://localhost:8080/api/payments
Body:
{
  "orderId": "${orderId}",
  "amount": "${amount}",
  "paymentMethod": "CARD"
}

Extractors:
- paymentId из response JSON

7. Gatling (для continuous load testing)

Где: Интеграция в CI/CD pipeline

class PaymentSimulation extends Simulation {
  val httpProtocol = http
    .baseUrl("http://localhost:8080")
    .acceptHeader("application/json")

  val scn = scenario("Payment Processing")
    .exec(http("Process Payment")
      .post("/api/payments")
      .body(StringBody("""
        {
          "orderId": "123",
          "amount": 1000
        }
      """))
      .check(status.is(200)))
    .pause(1)

  setUp(
    scn.inject(rampUsers(100).during(10.seconds))
  ).protocols(httpProtocol)
}

// Запуск:
// ./gradlew gatlingRun

// CI/CD: Fail build если P95 > 500ms

Процесс оптимизации, который я использую

1. MEASURE
   └─ Профилируй (Async Profiler)
   └─ Собирай метрики (Micrometer)

2. IDENTIFY
   └─ Найди bottleneck (CPU? Memory? I/O?)
   └─ Понимай, где тратится время/ресурсы

3. OPTIMIZE
   └─ Оптимизируй (код, кеш, DB, network)
   └─ Измеряй улучшение

4. VERIFY
   └─ Load test (JMeter, Gatling)
   └─ Убедись, что улучшение стабильно

5. MONITOR
   └─ Включи мониторинг в production
   └─ Настрой alerts (если P95 > threshold)

Реальный пример оптимизации

Проблема: Payment processing требует 5 секунд, нужно 500ms

Профилирование показало:

PaymentService.process()
├─ 40% CreditCardValidator.validate()
│   └─ Database query за каждый валидатор (5 validators = 5 queries)
├─ 35% ExternalAPI.charge()
│   └─ Synchronous REST call (network latency)
├─ 20% EmailService.notifyUser()
│   └─ Synchronous SMTP
└─ 5%  Other

Оптимизация:

// 1. Объединить N database queries в одну
// было: 5 queries → стало: 1 query (-3500ms)

// 2. Сделать ExternalAPI async
// было: synchronous → стало: CompletableFuture (-2000ms)

// 3. Email отправлять в background
// было: synchronous → стало: RabbitMQ event (-1000ms)

// Результат: 5000ms → 500ms (10x faster!)

Ключевые инструменты для разных задач

Для CPU profiling: Async Profiler (бесплатный, низкий overhead)

Для memory issues: YourKit или JProfiler (heap dumps, leak detection)

Для production monitoring: Micrometer + Prometheus + Grafana

Для load testing: Gatling (интегрируется в CI/CD)

Для локальной разработки: VisualVM (встроен в JDK)

Советы

  1. Всегда измеряй — не гадай, где проблема
  2. Используй бесплатные инструменты — Async Profiler достаточно хороший
  3. Профилируй в production-like среде — локально может быть другой результат
  4. Оптимизируй правильно — сначала найди bottleneck, потом оптимизируй
  5. После оптимизации — load test — убедись, что улучшение реально
  6. Мониторь постоянно — performance деградирует со временем