Warmup Class Load / JIT 계측 기록

실행 목적

  • AppWarmup 직접 호출 구간에서 추가로 로드되는 Spring / RDB / Redis / Cache / 구현체 주변 클래스를 확인한다.
  • 웜업 전후로 class load, JIT compilation, inlining, code cache 변화를 함께 계측한다.
  • 블로그 본문에 넣을 수 있는 공개 가능한 근거를 요약해 둔다.

실행 환경

  • JDK: OpenJDK 21.0.7
  • 로컬 컨테이너:
    • MySQL
    • Redis
  • 앱 실행 프로필: local
  • 앱 기동 방식: bootJar 실행

실제로 수행한 작업

  1. 로컬 MySQL/Redis를 컨테이너로 기동했다.
  2. 앱을 local 프로필로 실행했다.
  3. class+load*, compilation*, inlining*, codecache* 로그를 동시에 수집했다.
  4. 앱 로그에서 [Warmup] Starting warmup...[Warmup] Warmup completed, application is ready 사이를 잘라 웜업 구간을 분리했다.
  5. 웜업 전/중/후 통계를 집계했다.

Warmup 구간

  • Warmup 시작: 2026-03-21 11:58:53.291
  • Warmup 완료: 2026-03-21 11:58:53.938
  • Warmup 길이: 약 647ms

class load 집계

Warmup 전

  • 25,395
  • 구현 코드: 1,688
  • orm_query: 4,123
  • spring_rdb: 908
  • redis_lib: 575

관찰:

  • 핵심 Controller / Service / Repository / CacheService 본체와 Spring bean/CGLIB 프록시는 대부분 startup 단계에서 이미 로드됐다.
  • 즉 웜업은 “구현체 본체를 처음 로드하는 단계”라기보다, 요청 경로를 실제로 한 번 실행하면서 공통 경로를 더 데우는 단계에 가까웠다.
  • 실제 로그에서도 웜업 시작 시각보다 앞서 구현체와 프록시가 먼저 나타났다.
[2026-03-21T11:58:17.049+0900][info ][class,load] com.deartail.app.category.CategoryRepositoryImpl
[2026-03-21T11:58:17.053+0900][info ][class,load] com.deartail.app.component.service.ComponentServiceImpl
[2026-03-21T11:58:29.995+0900][info ][class,load] com.deartail.app.component.service.ComponentServiceImpl$$SpringCGLIB$$0

Warmup 중

  • 1,351
  • orm_query: 311
  • redis_lib: 273
  • spring_rdb: 40
  • 구현 코드: 65

Warmup 중에는 Spring / RDB / Cache, Hibernate / QueryDSL, Redis / Lettuce, 그리고 구현 코드 주변 클래스가 함께 관찰됐다.

[2026-03-21T11:58:53.351+0900][info ][class,load] org.springframework.orm.jpa.EntityManagerHolder
[2026-03-21T11:58:53.354+0900][info ][class,load] org.springframework.jdbc.datasource.DataSourceUtils
[2026-03-21T11:58:53.373+0900][info ][class,load] io.lettuce.core.protocol.DefaultEndpoint
[2026-03-21T11:58:53.391+0900][info ][class,load] com.deartail.app.component.service.ComponentServiceImpl$getHome$1

관찰:

  • 웜업이 실제로 추가로 당겨온 것은 Redis/Lettuce, Hibernate/QueryDSL, Spring transaction/jdbc/cache 경로였다.
  • 우리 코드 쪽은 구현체 본체보다 lambda, DTO, 응답 모델, 정렬/코루틴 보조 클래스가 추가 로드됐다.

Warmup 후

  • 140
  • 대부분 shutdown/connection teardown 관련 클래스였다.

JIT compilation 집계

Warmup 중

  • 712

Warmup 중 JIT compilation은 Spring transaction, QueryDSL, Jackson 같은 공통 경로가 중심이었다.

관찰:

  • 웜업 중 com.deartail... 구현체 메서드가 JIT compilation 상위권에 거의 보이지 않았다.
  • 이번 구조에서 JIT의 직접 수혜는 Spring / Hibernate / QueryDSL / Lettuce / Jackson 경로에 더 먼저 나타났다.

inlining 집계

Warmup 중

  • 5,053

Warmup 중 실제 JIT 로그 발췌:

[2026-03-21T11:58:53.386+0900][debug][jit,compilation] com.querydsl.core.types.Templates::add
[2026-03-21T11:58:53.387+0900][debug][jit,inlining] com.querydsl.core.types.TemplateFactory::create callee is too large
[2026-03-21T11:58:53.626+0900][debug][jit,compilation] org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource::getTransactionAttribute
[2026-03-21T11:58:53.637+0900][debug][jit,inlining] org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource::getCacheKey inline (hot)
[2026-03-21T11:58:53.664+0900][debug][jit,compilation] org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut::matches
[2026-03-21T11:58:53.664+0900][debug][jit,inlining] org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource::getTransactionAttribute already compiled into a big method
[2026-03-21T11:58:53.854+0900][debug][jit,compilation] com.fasterxml.jackson.databind.JavaType::getRawClass

관찰:

  • 인라인도 주로 Spring core / transaction / QueryDSL / JDK 공통 경로에서 활발하게 나타났다.
  • 웜업 한 번으로 도메인 메서드 자체가 대거 최적화되는 그림은 아니었다.

code cache 스냅샷

Warmup 직전 마지막 관찰값:

  • non-profiled nmethods: 11,694KB
  • profiled nmethods: 36,504KB
  • non-nmethods: 1,992KB

Warmup 중 마지막 관찰값:

  • non-profiled nmethods: 11,487KB
  • profiled nmethods: 33,359KB
  • non-nmethods: 2,032KB

Warmup 후 마지막 관찰값:

  • non-profiled nmethods: 11,718KB
  • profiled nmethods: 33,464KB
  • non-nmethods: 2,034KB

주의:

  • code cache used 값은 sweep/deoptimization 영향으로 단순 누적처럼 증가하지 않을 수 있다.
  • 여기서는 절대값보다 웜업 창 안에서도 코드 캐시 활동이 있었다는 근거로 보는 편이 안전하다.

해석

  1. Warmup 전에 이미 핵심 Controller / Service / Repository / CacheService 구현체와 CGLIB 프록시 상당수는 startup 단계에서 로드된다.
  2. Warmup이 추가로 소진하는 비용은 Redis/Lettuce, Hibernate/QueryDSL, Spring transaction/jdbc/cache, 그리고 lambda/DTO/응답 모델 같은 요청 경로 주변 클래스가 중심이다.
  3. Warmup 창 안에서도 JIT compilation과 inlining은 분명히 발생한다.
  4. 하지만 이번 구조에서 JIT의 주된 대상은 우리 비즈니스 메서드보다 Spring / Hibernate / QueryDSL / Redis / Jackson 경로에 가깝다.
  5. 따라서 글의 핵심 메시지는 여전히 요청 경로의 초기화 지연을 고객 요청 전에 미리 소진한다가 맞고, JIT는 그 위에 따라오는 보조 효과로 설명하는 편이 정확하다.