이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Class Loading JIT Runtime에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • JVM은 실행 중에도 최적화한다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 Class Loading JIT Runtime 판단 기준은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 Class Loading JIT Runtime이다. Class loading, interpreter, JIT, warm-up이 Java 서버 실행에 미치는 영향을 정리한다.

먼저 붙잡을 기준은 다음 문장이다. JVM은 실행 중에도 최적화한다. Spring Boot 서버는 시작 시 class loading과 bean 초기화 비용을 치르고, 실행 중에는 JIT가 hot code를 최적화한다. 그래서 같은 코드도 startup 직후, warm-up 이후, class 재로딩이나 code cache 압박 상황에서 latency가 다르게 보일 수 있다.

핵심 모델

  • 판단 기준: JVM은 실행 중에도 최적화한다.
  • JVM은 class loading, bytecode 실행, JIT, heap, GC, thread, native interface를 관리하는 runtime이다.
  • Class loading과 JIT는 startup, warm-up, code cache, reflection 사용량과 연결된다.
  • GC는 memory 회수를 수행하지만 stop-the-world pause와 CPU 사용량을 만들 수 있다.
  • JFR, JMX, Actuator, jcmd는 JVM 내부 상태를 운영 지표와 dump로 바꾸는 관찰 경로다.
  • JVM option은 성능 마법값이 아니라 heap, GC, dump, log, container limit에 대한 운영 계약이다.

문서별 핵심 구분

  • class loading은 startup과 동적 로딩 비용에 영향을 준다.
  • JIT는 hot code를 최적화하지만 warm-up 시간을 만든다.
  • runtime 최적화 상태에 따라 같은 코드의 latency가 달라질 수 있다.

원리

Class loading은 class 파일을 읽고 검증하고 runtime에 연결하는 과정이다. Spring Boot startup에서는 annotation scanning, proxy 생성, reflection, JPA entity scan 같은 작업이 이 비용을 키운다.

JVM은 처음부터 모든 코드를 native code로 최적화하지 않는다. 자주 실행되는 hot path를 관찰한 뒤 JIT compile을 수행하므로, 배포 직후 warm-up 전 latency와 CPU 사용은 안정 상태와 다를 수 있다.

JIT 최적화 결과는 code cache에 저장된다. Code cache 압박이나 deoptimization이 생기면 CPU가 튀거나 특정 endpoint latency가 흔들릴 수 있다.

따라서 JVM runtime 문제는 “CPU가 높다”에서 멈추지 않고 startup인지 warm-up인지, class loading인지 JIT compile인지, GC와 섞인 CPU인지 분리해야 한다.

Java/Spring 연결

  • Spring Boot startup은 class loading, reflection, proxy, annotation scanning 비용을 크게 사용한다.
  • AOP, JPA, JSON 직렬화, validation은 runtime reflection과 allocation 패턴에 영향을 줄 수 있다.
  • Actuator jvm.* 지표는 JVM 내부를 HTTP로 노출하지만 원인 분석에는 jcmd, JFR, GC log가 함께 필요하다.
  • Container에서 -Xmx만 크게 잡으면 native memory와 OS page cache 여유가 사라질 수 있다.
  • JVM option 변경은 배포 artifact 변경만큼 추적 가능해야 한다.

코드와 설정 예시

jcmd <pid> VM.flags
jcmd <pid> VM.command_line
jcmd <pid> VM.classloader_stats
jcmd <pid> Compiler.CodeHeap_Analytics

예시는 실제 JVM option, classloader 통계, code cache 상태를 확인하는 흐름이다. Startup 지연은 class loading/JPA scan 로그와 맞추고, warm-up 중 CPU 상승은 JFR의 compilation event와 함께 확인한다.

관찰 명령

jcmd <pid> VM.classloader_stats
jcmd <pid> Compiler.CodeHeap_Analytics
jcmd <pid> VM.flags

Class Loading JIT Runtime 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.

지표 해석

신호먼저 의심할 것다음 확인
startup 지연class loading, reflection, bean 초기화JFR startup profile, logs
GC pause spikeheap 압박, allocation burstGC log, jvm.gc.pause
CPU 급등JIT, GC, busy code pathJFR, top -H, thread dump
RSS가 Xmx보다 큼metaspace, direct, stack, nativeNMT, process memory

배포 직후 class loading, JIT compilation CPU, warm-up 이후 latency 하락을 한 타임라인에 놓아야 startup 비용과 지속 장애를 구분할 수 있다.

장애 상황에서 보는 순서

  1. jcmd <pid> VM.command_line으로 실제 JVM option을 확인한다.
  2. Heap과 non-heap, RSS를 분리해서 본다.
  3. GC pause 시간대와 API latency spike를 맞춘다.
  4. 필요하면 짧은 JFR을 duration 제한으로 수집한다.
  5. 옵션 변경 전후의 지표와 부하 조건을 기록한다.

이 순서는 JVM runtime 후보를 나누기 위한 출발점이다. startup, warm-up, GC, option 문제는 서로 겹쳐 보일 수 있으므로 JFR/JVM 지표와 애플리케이션 latency를 같이 맞춘다.

실전 팁

  • 배포 직후 latency는 steady state latency와 분리해서 본다.
  • Startup 지연은 class loading, bean 초기화, DB migration, external dependency 연결을 시간대별로 나눈다.
  • JIT warm-up을 의심하면 같은 endpoint의 반복 호출 latency와 CPU compile event를 함께 본다.
  • Code cache 관련 경고가 있으면 option 변경보다 실제 code heap 사용량과 deoptimization 여부를 먼저 확인한다.
  • 장애 회고에는 JVM option, 배포 직후 시간, warm-up 상태, JFR/JIT 증거를 남긴다.

주니어 팁

  • JVM은 실행하면서 hot code를 찾아 최적화하므로 배포 직후와 안정 상태가 다를 수 있다.
  • Startup이 느리면 class loading만 보지 말고 Spring bean 초기화와 외부 연결도 나눠 본다.
  • JIT 문제를 의심할 때는 느낌보다 JFR, code cache, CPU 시간대 증거를 남긴다.

시니어 팁

  • 배포 전략에는 warm-up, readiness, 트래픽 전환 시점을 포함한다.
  • Startup 최적화는 classpath, bean 생성, lazy initialization, native image 같은 선택지의 운영 trade-off를 함께 본다.
  • JVM option 변경은 code cache, compiler thread, tiered compilation의 효과와 부작용을 지표로 검증한다.

Guru급 팁

  • 배포 파이프라인에는 warm-up 관찰 구간을 둔다. readiness가 열렸다는 사실과 JIT가 안정됐다는 사실은 다르다.
  • Startup JFR은 운영 JFR과 목적이 다르다. class loading, bean creation, compiler event를 보기 위한 별도 profile로 남기는 편이 좋다.
  • Code cache와 compiler thread 지표를 장기 추세로 보면 특정 릴리스 이후 runtime 최적화 비용이 늘었는지 추적할 수 있다.

위험 신호!

  • 배포 직후 latency spike를 정상 상태 성능으로 오해한다.
  • Startup 지연을 모두 JVM class loading 탓으로 돌리고 Spring bean 초기화 로그를 보지 않는다.
  • JIT warm-up을 확인하지 않고 CPU 급등을 application bug로 단정한다.
  • Code cache 경고를 무시하고 장기 실행 중 성능 변동을 놓친다.
  • JVM option을 바꾸고 전후 VM.flags, JFR, latency 조건을 남기지 않는다.

확인 질문

확인 질문

  • 배포 직후 latency와 안정 상태 latency를 분리해야 하는 이유는 무엇인가?
    • Class loading, bean 초기화, JIT warm-up이 아직 끝나지 않아 같은 코드도 초기에는 다른 성능을 보일 수 있기 때문이다.
  • JIT warm-up을 의심할 때 어떤 증거를 봐야 하는가?
    • 반복 호출 latency 변화, CPU 시간대, JFR compilation event, code cache 상태를 함께 봐야 한다.
  • JVM option 변경 전후에 무엇을 남겨야 하는가?
    • VM.command_line, VM.flags, 부하 조건, latency, CPU, JFR/GC log 같은 전후 비교 증거를 남겨야 한다.

참고 문서