Spring Boot + Micrometer로 JVM 메모리 대시보드 구축하기

"☕" 10 "min read"

GC 로그, Heap Dump, JFR로 문제를 사후에 분석하는 방법은 이제 알게 되었습니다. 이번 글에서는 한 발 앞서서, 문제가 생기기 전에 이상 징후를 포착하는 실시간 JVM 모니터링 대시보드를 직접 구축합니다. Spring Boot Actuator → Micrometer → Prometheus → Grafana로 이어지는 파이프라인 전체를 처음부터 끝까지 구성하고, 이 시리즈에서 다뤘던 Off-Heap, GC, Virtual Thread 지표를 하나의 화면에서 볼 수 있도록 만들어 봅니다.

👉 JVM GC 로그 분석 실전 — GCViewer와 GCEasy로 튜닝 시작하기

👉 Java 21 Virtual Thread 완전 정리 — 메모리 모델, Pinning, Spring Boot 적용


1. 전체 구조 이해하기

본격적으로 설정하기 전에 각 컴포넌트가 어떤 역할을 하는지 먼저 파악합니다.

JVM 모니터링 파이프라인 아키텍처Spring Boot Actuator에서 Prometheus가 pull하고 Grafana에서 시각화하여 Slack/이메일 알림으로 이어지는 파이프라인Spring Boot 앱JVM + Micrometer/actuator/prometheus15초 pullPrometheus시계열 DB 저장메트릭 수집PromQL 쿼리Grafana대시보드 시각화알람 규칙 설정알람 발송알림Slack 메시지이메일 발송

각 컴포넌트 역할 한 줄 요약

컴포넌트역할
MicrometerJVM 내부 수치를 측정해 다양한 모니터링 시스템 형식으로 변환하는 측정 파사드
Spring Boot Actuator앱 내부 상태를 HTTP 엔드포인트로 노출 (/actuator/prometheus 등)
Prometheus15초 간격으로 /actuator/prometheus에서 메트릭을 당겨(pull) 시계열 DB에 저장
GrafanaPrometheus에 PromQL로 쿼리해 그래프로 시각화, 알람 규칙 설정

2. Spring Boot 의존성 및 설정

build.gradle / pom.xml 의존성 추가

// build.gradle
dependencies {
    // Spring Boot Actuator: /actuator/* 엔드포인트 활성화
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    // Micrometer Prometheus 레지스트리: /actuator/prometheus 엔드포인트 활성화
    implementation 'io.micrometer:micrometer-registry-prometheus'

    // (선택) AOP 기반 메서드 타이밍 측정 시 필요
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}Code language: JavaScript (javascript)
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>Code language: HTML, XML (xml)

application.yml 설정

# application.yml
management:
  endpoints:
    web:
      exposure:
        # prometheus, health는 최소한으로 노출
        # 운영 환경에서는 네트워크 레벨로 외부 접근 차단 권장
        include: prometheus, health, info, metrics

  endpoint:
    prometheus:
      enabled: true
    health:
      show-details: when-authorized

  metrics:
    # JVM 관련 메트릭 전체 활성화
    enable:
      jvm: true
      process: true
      system: true
      tomcat: true

    # 모든 메트릭에 공통 태그 추가 (Grafana에서 필터링에 사용)
    tags:
      application: ${spring.application.name}
      environment: ${spring.profiles.active:local}

  # Prometheus 히스토그램 활성화 (p99 레이턴시 계산에 필요)
  observations:
    http:
      server:
        requests:
          enabled: true

spring:
  application:
    name: my-spring-app

  # Virtual Thread 활성화 (Java 21 + Spring Boot 3.2)
  threads:
    virtual:
      enabled: trueCode language: PHP (php)

설정 확인

# 앱 시작 후 메트릭 엔드포인트 확인
curl http://localhost:8080/actuator/prometheus | head -50

# 출력 예시:
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
# jvm_memory_used_bytes{application="my-spring-app",area="heap",id="G1 Eden Space",} 1.23456789E8
# jvm_memory_used_bytes{application="my-spring-app",area="heap",id="G1 Old Gen",} 5.67890123E8
# jvm_memory_used_bytes{application="my-spring-app",area="nonheap",id="Metaspace",} 8.9012345E7
# ...Code language: PHP (php)

3. 커스텀 비즈니스 메트릭 추가

JVM 기본 메트릭 외에도 비즈니스 로직과 연결된 커스텀 메트릭을 추가하면 "주문 처리 속도가 느려질 때 GC가 같이 증가하는가?" 같은 상관관계 분석이 가능해집니다.

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.atomic.AtomicInteger;

@Service
@Slf4j
public class OrderMetricsService {

    // 주문 처리 타이머 (p50, p95, p99 자동 계산)
    private final Timer orderProcessingTimer;

    // 주문 성공/실패 카운터
    private final Counter orderSuccessCounter;
    private final Counter orderFailureCounter;

    // 현재 처리 중인 주문 수 (실시간 Gauge)
    private final AtomicInteger activeOrders = new AtomicInteger(0);

    public OrderMetricsService(MeterRegistry registry) {
        // Timer: 처리 시간 측정, 백분위 히스토그램 활성화
        this.orderProcessingTimer = Timer.builder("order.processing.duration")
            .description("주문 처리 소요 시간")
            .tag("service", "order")
            .publishPercentiles(0.5, 0.95, 0.99)    // p50, p95, p99 계산
            .publishPercentileHistogram(true)         // Prometheus 히스토그램 형식
            .register(registry);

        // Counter: 단조 증가 카운터
        this.orderSuccessCounter = Counter.builder("order.processed.total")
            .description("처리 완료된 주문 총 수")
            .tag("status", "success")
            .register(registry);

        this.orderFailureCounter = Counter.builder("order.processed.total")
            .description("처리 실패한 주문 총 수")
            .tag("status", "failure")
            .register(registry);

        // Gauge: 현재 값을 실시간으로 반영
        Gauge.builder("order.active.count", activeOrders, AtomicInteger::get)
            .description("현재 처리 중인 주문 수")
            .register(registry);
    }

    public Order processOrder(OrderRequest request) {
        activeOrders.incrementAndGet();  // 처리 시작: +1

        return orderProcessingTimer.record(() -> {  // 실행 시간 자동 측정
            try {
                Order result = doProcessOrder(request);
                orderSuccessCounter.increment();
                return result;
            } catch (Exception e) {
                orderFailureCounter.increment();
                throw e;
            } finally {
                activeOrders.decrementAndGet();  // 처리 완료: -1
            }
        });
    }
}Code language: PHP (php)
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

import java.lang.management.BufferPoolMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;

/**
 * Off-Heap (DirectByteBuffer) 사용량을 Micrometer 메트릭으로 노출
 * 이전 글에서 다뤘던 DirectByteBuffer 누수를 대시보드에서 모니터링
 */
@Component
public class OffHeapMetricsRegistrar {

    public OffHeapMetricsRegistrar(MeterRegistry registry) {
        List<BufferPoolMXBean> pools =
            ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);

        for (BufferPoolMXBean pool : pools) {
            String poolName = pool.getName();  // "direct" 또는 "mapped"

            // Direct Buffer 사용량 (bytes)
            io.micrometer.core.instrument.Gauge.builder(
                    "jvm.buffer.offheap.used",
                    pool,
                    p -> (double) p.getMemoryUsed()
                )
                .tag("pool", poolName)
                .description("Off-Heap " + poolName + " 버퍼 사용량 (bytes)")
                .register(registry);

            // Direct Buffer 개수
            io.micrometer.core.instrument.Gauge.builder(
                    "jvm.buffer.offheap.count",
                    pool,
                    p -> (double) p.getCount()
                )
                .tag("pool", poolName)
                .description("Off-Heap " + poolName + " 버퍼 개수")
                .register(registry);
        }
    }
}Code language: JavaScript (javascript)

4. Prometheus + Grafana Docker Compose 구성

개발/스테이징 환경에서 빠르게 구성하는 방법입니다.

# docker-compose.monitoring.yml
version: '3.8'

services:
  # Prometheus: 메트릭 수집 및 저장
  prometheus:
    image: prom/prometheus:v2.50.0
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./monitoring/alert-rules.yml:/etc/prometheus/alert-rules.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'   # 15일치 데이터 보관
      - '--web.enable-lifecycle'               # 설정 파일 핫 리로드 가능

  # Grafana: 시각화 대시보드
  grafana:
    image: grafana/grafana:10.3.0
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123   # 실제 운영 시 변경 필수
      - GF_USERS_ALLOW_SIGN_UP=false

volumes:
  prometheus_data:
  grafana_data:Code language: PHP (php)
# monitoring/prometheus.yml
global:
  scrape_interval: 15s       # 15초마다 메트릭 수집
  evaluation_interval: 15s   # 알람 규칙 평가 주기

# 알람 규칙 파일 참조
rule_files:
  - "alert-rules.yml"

# 알람을 받을 Alertmanager (선택)
# alerting:
#   alertmanagers:
#     - static_configs:
#         - targets: ['alertmanager:9093']

scrape_configs:
  # Spring Boot 앱 메트릭 수집
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets:
          - 'host.docker.internal:8080'   # 로컬 개발 시
          # - 'app-server-1:8080'         # 운영 서버 IP로 교체
          # - 'app-server-2:8080'
    # 기본 인증이 필요한 경우
    # basic_auth:
    #   username: 'actuator-user'
    #   password: 'actuator-pass'Code language: PHP (php)
# monitoring/alert-rules.yml — 알람 규칙 정의
groups:
  - name: jvm-alerts
    rules:

      # Heap Old Gen 사용률 85% 초과 10분 지속 → 경고
      - alert: HeapOldGenHighUsage
        expr: |
          (
            jvm_memory_used_bytes{area="heap", id=~".*[Oo]ld.*"}
            / jvm_memory_max_bytes{area="heap", id=~".*[Oo]ld.*"}
          ) > 0.85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Heap Old Gen 사용률 85% 초과 ({{ $labels.application }})"
          description: "OOM 발생 전 Heap Dump 수집 또는 GC 튜닝이 필요합니다."

      # GC Pause 시간 평균 500ms 초과 5분 지속 → 경고
      - alert: GCPauseTimeTooLong
        expr: |
          rate(jvm_gc_pause_seconds_sum[5m])
          / rate(jvm_gc_pause_seconds_count[5m]) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "GC Pause 평균 500ms 초과 ({{ $labels.application }})"
          description: "GC 로그 분석 또는 GC 알고리즘 전환을 검토하세요."

      # Direct Buffer 사용량 1GB 초과 → 경고 (Off-Heap 누수 감지)
      - alert: DirectBufferHighUsage
        expr: |
          jvm_buffer_offheap_used{pool="direct"} > 1073741824
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Direct Buffer 사용량 1GB 초과 ({{ $labels.application }})"
          description: "Off-Heap 메모리 누수를 의심하세요. DirectByteBuffer 분석이 필요합니다."

      # 스레드 수 급증 → 경고 (스레드 누수 감지)
      - alert: ThreadCountHighUsage
        expr: jvm_threads_live_threads > 500
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "JVM 스레드 수 500개 초과 ({{ $labels.application }})"
          description: "스레드 누수 또는 스레드풀 설정을 점검하세요."Code language: PHP (php)

5. Grafana 데이터소스 및 대시보드 프로비저닝

# monitoring/grafana/datasources/prometheus.yml
# Grafana 시작 시 자동으로 Prometheus 데이터소스 등록
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    url: http://prometheus:9090
    isDefault: true
    editable: falseCode language: PHP (php)
# monitoring/grafana/dashboards/dashboard.yml
# 대시보드 JSON 파일 자동 로드 설정
apiVersion: 1
providers:
  - name: JVM Dashboards
    type: file
    options:
      path: /etc/grafana/provisioning/dashboardsCode language: PHP (php)

6. PromQL 핵심 쿼리 모음 — 패널 구성하기

Grafana 패널을 구성할 때 사용하는 PromQL 쿼리입니다. 각 쿼리를 패널에 붙여넣기만 하면 됩니다.

Heap 메모리 패널

# Heap 영역별 사용량 (MB 단위 변환)
jvm_memory_used_bytes{application="$app", area="heap"}
/ 1024 / 1024

# Heap 사용률 (%)
(
  sum(jvm_memory_used_bytes{application="$app", area="heap"})
  /
  sum(jvm_memory_max_bytes{application="$app", area="heap"})
) * 100

# Old Gen 사용률 (OOM 예측에 핵심)
(
  jvm_memory_used_bytes{application="$app", id=~".*[Oo]ld.*"}
  /
  jvm_memory_max_bytes{application="$app", id=~".*[Oo]ld.*"}
) * 100Code language: PHP (php)

GC 패널

# GC 발생 횟수 (분당)
rate(jvm_gc_pause_seconds_count{application="$app"}[1m]) * 60

# GC 평균 Pause 시간 (ms)
(
  rate(jvm_gc_pause_seconds_sum{application="$app"}[5m])
  /
  rate(jvm_gc_pause_seconds_count{application="$app"}[5m])
) * 1000

# GC 종류별 Pause 시간 비교
rate(jvm_gc_pause_seconds_sum{application="$app"}[5m])
  by (action, cause)Code language: PHP (php)

Off-Heap (DirectByteBuffer) 패널

# Direct Buffer 사용량 (MB)
jvm_buffer_offheap_used{application="$app", pool="direct"}
/ 1024 / 1024

# Direct Buffer 개수 (누수 시 지속 증가)
jvm_buffer_offheap_count{application="$app", pool="direct"}Code language: PHP (php)

스레드 패널

# 전체 라이브 스레드 수
jvm_threads_live_threads{application="$app"}

# 상태별 스레드 수 (RUNNABLE, BLOCKED, WAITING 등)
jvm_threads_states_threads{application="$app"}

# 데몬 스레드 비율
jvm_threads_daemon_threads{application="$app"}
/ jvm_threads_live_threads{application="$app"} * 100Code language: PHP (php)

HTTP 요청 성능 패널

# 초당 요청 수 (RPS)
rate(http_server_requests_seconds_count{application="$app"}[1m])

# p99 응답 시간 (ms)
histogram_quantile(0.99,
  rate(http_server_requests_seconds_bucket{application="$app"}[5m])
) * 1000

# 엔드포인트별 p99 응답 시간
histogram_quantile(0.99,
  rate(http_server_requests_seconds_bucket{
    application="$app",
    uri!~"/actuator.*"   # Actuator 엔드포인트 제외
  }[5m])
) by (uri, method) * 1000

# 에러율 (4xx + 5xx 비율)
sum(rate(http_server_requests_seconds_count{
  application="$app", status=~"[45].."
}[5m]))
/
sum(rate(http_server_requests_seconds_count{
  application="$app"
}[5m])) * 100Code language: PHP (php)

비즈니스 메트릭 패널

# 주문 처리 초당 성공/실패 건수
rate(order_processed_total{application="$app"}[1m])

# 주문 처리 p99 시간 (ms)
histogram_quantile(0.99,
  rate(order_processing_duration_seconds_bucket{application="$app"}[5m])
) * 1000

# 현재 처리 중인 주문 수
order_active_count{application="$app"}Code language: PHP (php)

7. Grafana 대시보드 레이아웃 구성

Grafana JVM 메모리 대시보드 레이아웃5개 Row로 구성된 Grafana 대시보드 — 전체 현황, Heap 상세, GC 상세, 스레드/HTTP, 비즈니스 메트릭JVM Memory Dashboardapp = my-spring-appLast 1h ▼ROW 1 — 전체 현황Heap 사용률주의68%GC Pause (avg)정상82msRPS정상1,240에러율정상0.3%ROW 2 — Heap 상세영역별 Heap 사용량Old Gen 추이Direct BufferOff-HeapROW 3 — GC 상세GC 횟수/분GC Pause 시간 (ms)GC 원인 분포Young 72%MixedFullROW 4 — 스레드 & HTTPVirtual Thread 수12,840HTTP 응답시간 p50 / p95 / p99 (ms)18p5082p95210p99ROW 5 — 비즈니스 메트릭주문 TPS34.2 req/s주문 처리 p99145 ms처리 중 주문 수28

8. Grafana 대시보드 JSON 핵심 패널 예시

직접 Grafana UI에서 패널을 만드는 대신, JSON으로 임포트할 수 있습니다. 아래는 Heap 사용률 패널과 GC Pause 패널의 JSON 예시입니다.

{
  "title": "JVM Memory Dashboard",
  "panels": [
    {
      "title": "Heap Old Gen 사용률 (%)",
      "type": "timeseries",
      "gridPos": { "x": 0, "y": 8, "w": 12, "h": 8 },
      "targets": [
        {
          "expr": "(jvm_memory_used_bytes{application=\"$app\", id=~\".*[Oo]ld.*\"} / jvm_memory_max_bytes{application=\"$app\", id=~\".*[Oo]ld.*\"}) * 100",
          "legendFormat": "Old Gen 사용률"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "thresholds": {
            "steps": [
              { "color": "green", "value": 0 },
              { "color": "yellow", "value": 70 },
              { "color": "red", "value": 85 }
            ]
          }
        }
      },
      "options": {
        "tooltip": { "mode": "multi" }
      }
    },
    {
      "title": "GC Pause 시간 (ms)",
      "type": "timeseries",
      "gridPos": { "x": 12, "y": 8, "w": 12, "h": 8 },
      "targets": [
        {
          "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$app\"}[5m]) / rate(jvm_gc_pause_seconds_count{application=\"$app\"}[5m]) * 1000",
          "legendFormat": "평균 GC Pause (ms)"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "ms",
          "thresholds": {
            "steps": [
              { "color": "green", "value": 0 },
              { "color": "yellow", "value": 200 },
              { "color": "red", "value": 500 }
            ]
          }
        }
      }
    }
  ],
  "templating": {
    "list": [
      {
        "name": "app",
        "type": "query",
        "query": "label_values(jvm_memory_used_bytes, application)",
        "label": "Application"
      }
    ]
  }
}Code language: JSON / JSON with Comments (json)

Grafana 커뮤니티 대시보드 ID 4701 (JVM Micrometer)를 임포트하면 기본 JVM 대시보드를 빠르게 시작점으로 활용할 수 있습니다. Dashboards > Import > ID 4701 입력 후 본 글의 패널을 추가하는 방식을 권장합니다.


9. 알람 → Slack 연동

# monitoring/alertmanager.yml (Alertmanager 사용 시)
global:
  slack_api_url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'

route:
  group_by: ['alertname', 'application']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'slack-jvm-alerts'

receivers:
  - name: 'slack-jvm-alerts'
    slack_configs:
      - channel: '#jvm-alerts'
        title: 'JVM 알람: {{ .GroupLabels.alertname }}'
        text: |
          *애플리케이션*: {{ .GroupLabels.application }}
          *심각도*: {{ .CommonLabels.severity }}
          {{ range .Alerts }}
          *요약*: {{ .Annotations.summary }}
          *설명*: {{ .Annotations.description }}
          *시각*: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
          {{ end }}
        send_resolved: true   # 알람 해소 시에도 알림Code language: PHP (php)

또는 Grafana 자체 알람 기능을 사용할 수도 있습니다 (Alertmanager 불필요).

# Grafana Contact Point 설정 (Grafana UI에서 설정 가능)
# Alerting > Contact points > Add contact point
# Type: Slack
# Webhook URL: https://hooks.slack.com/services/...Code language: PHP (php)

10. 이 시리즈에서 다룬 지표 전체 정리

지금까지 이 시리즈에서 다룬 모든 문제와 그것을 감지하는 메트릭을 정리합니다.

JVM 시리즈 전체 문제-메트릭-편 정리발생 가능한 JVM 문제 5가지와 그것을 감지하는 메트릭, 참고 편 번호 매핑 다이어그램발생 가능 문제감지 메트릭Off-Heap 누수DirectByteBuffer 증가jvm_buffer_offheap_usedjcmd, pmap, NativeMemoryTracking1OOM / Heap 누수Old Gen 우상향jvm_memory_used_bytes (heap area)Heap Dump + Eclipse MAT 분석2, 3GC Stop-The-World 과다레이턴시 급등jvm_gc_pause_seconds_sum/countGC 로그 -Xlog:gc*, GCViewer, GCEasy4CPU/메모리 핫스팟특정 시간대 성능 저하JFR Allocations, Method ProfilingJMC Automated Analysis5

11. 추가 고려사항

Prometheus 메트릭 카디널리티 주의

태그(label)의 값 조합이 너무 많으면 Prometheus 메모리 사용량이 폭발합니다. 예를 들어 userId를 태그로 쓰면 사용자 수만큼 시계열이 생성됩니다.

// 카디널리티 폭발: userId를 태그로 사용
Counter.builder("order.processed.total")
    .tag("userId", userId)   // 사용자 수 = 시계열 수
    .register(registry);

// 집계 가능한 태그만 사용
Counter.builder("order.processed.total")
    .tag("userType", user.getType())  // "PREMIUM" / "STANDARD" 등 고정값
    .register(registry);Code language: JavaScript (javascript)

Micrometer Tracing — 분산 추적 연동

Spring Boot 3.x와 Micrometer Tracing을 조합하면 Zipkin이나 Jaeger로 요청 흐름을 분산 추적할 수 있습니다. 특정 요청의 어느 구간에서 시간이 소요되는지 TraceId로 추적할 수 있어 JVM 메트릭과 함께 사용하면 강력합니다.

<!-- 분산 추적 의존성 추가 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>Code language: HTML, XML (xml)

운영 환경 보안

/actuator/prometheus를 외부에 노출할 때는 반드시 보안 처리를 해야 합니다.

# 방법 1: 별도 포트에서만 Actuator 노출 (권장)
management:
  server:
    port: 9090   # 앱 포트(8080)와 분리, 방화벽으로 외부 차단
  endpoints:
    web:
      exposure:
        include: prometheus, healthCode language: PHP (php)

👉 Java Off-Heap 메모리와 DirectByteBuffer 누수 탐지 실전 가이드

👉 JVM GC 로그 분석 실전 — GCViewer와 GCEasy로 튜닝 시작하기


12. 마무리

이번 글을 세 줄로 정리합니다.

  1. Spring Boot Actuator + Micrometer + Prometheus + Grafana 파이프라인으로 JVM 내부 지표를 실시간으로 시각화하면, 문제가 발생하기 전에 이상 징후를 포착할 수 있습니다.
  2. Off-Heap 누수, GC Pause, 스레드 이상, HTTP 레이턴시를 한 대시보드에서 함께 보면 각 지표 간 상관관계를 발견할 수 있어 근본 원인 분석이 훨씬 빠릅니다.
  3. 알람 규칙(Old Gen 85%, GC Pause 500ms, Direct Buffer 1GB)을 설정해두면 야간이나 주말에 발생하는 장애도 조기에 대응할 수 있습니다.

시리즈 마무리

이 시리즈에서 다룬 내용을 한 줄씩 정리합니다.

주제핵심 내용
1Off-Heap / DirectByteBuffer 누수 탐지jcmd, pmap, NativeMemoryTracking으로 GC 밖 메모리 추적
2ZGC vs G1GC Off-Heap 차이GC 선택에 따른 Direct Buffer 해제 메커니즘 차이
3Heap Dump 자동 수집과 분석-XX:+HeapDumpOnOutOfMemoryError, Eclipse MAT 파이프라인
4GC 로그 분석 실전-Xlog:gc*, GCViewer/GCEasy, 증상별 튜닝
5JFR + JMC 운영 프로파일링1~2% 오버헤드로 핫스팟/병목 실시간 분석
6Java 21 Virtual Thread메모리 모델, Pinning, Spring Boot 3.2 적용
7Micrometer + Grafana 대시보드모든 JVM 지표를 한 화면에서 모니터링

조회수: 1