시리즈 구성
- 1편: 개념과 동작 원리
- 2편: Spring Boot 실전 적용 ← 현재 글
- 3편: JDK 배포판 비교와 실무 선택 가이드
들어가며
1편에서 CRaC의 개념과 동작 원리를 살펴봤습니다.
https://dmoritle.tistory.com/248
[JVM] CRaC 완전 정복 (1편) - 개념과 동작 원리
시리즈 구성1편: 개념과 동작 원리 ← 현재 글2편: Spring Boot 실전 적용3편: JDK 배포판 비교와 실무 선택 가이드 들어가며Java 애플리케이션은 왜 뜨는 데 오래 걸릴까요? Spring Boot 기반의 마이크로
dmoritle.tistory.com
이번 편에서는 Spring Boot 애플리케이션에 CRaC를 실제로 적용하는 방법을 다룹니다.
Spring Boot 3.2 / Spring Framework 6.1부터 CRaC를 공식 지원합니다. 프레임워크가 리소스 라이프사이클을 자동으로 관리해주기 때문에, 대부분의 경우 의존성 추가만으로 체크포인트 적용이 가능합니다.
1. Spring Boot CRaC 지원 구조
Spring이 자동으로 처리하는 것
1편에서 Resource 인터페이스를 직접 구현해야 한다고 설명했습니다. Spring Boot 3.2+에서는 이 작업을 Spring이 대신 처리합니다.
Spring의 Lifecycle 인터페이스가 CRaC의 beforeCheckpoint / afterRestore와 동일한 역할을 수행합니다.

Spring은 소켓, 파일 핸들, 스레드 풀 등 제한된 범위(limited scope) 내에서 라이프사이클을 자동으로 관리합니다. Spring에 의존하지 않는 라이브러리가 소켓이나 파일 핸들을 직접 열고 있다면 org.crac.Resource를 별도로 구현해야 할 수 있습니다.
의존성 추가
<!-- Maven -->
<dependency>
<groupId>org.crac</groupId>
<artifactId>crac</artifactId>
<version>1.5.0</version>
</dependency>
// Gradle (Kotlin DSL)
implementation("org.crac:crac:1.5.0")
이 의존성 하나가 추가되면 Spring이 체크포인트 전후 소켓·커넥션 처리를 자동으로 조율합니다. Spring Framework 공식 문서 기준으로 org.crac 1.4.0 이상이면 지원됩니다.
참고: Spring Boot 3.2 이전 버전에서는
org.crac의존성 없이jcmd로 체크포인트를 시도하면CheckpointOpenSocketException이 발생합니다. 이 의존성 추가가 해결책입니다.
2. 두 가지 체크포인트 모드
모드 1: On-demand 체크포인트 (수동)
실행 중인 애플리케이션에 jcmd로 체크포인트를 명령하는 방식입니다. 웜업 트래픽을 충분히 발생시킨 뒤 체크포인트를 생성할 수 있어 JIT가 최대로 웜업된 상태를 이미지에 담을 수 있습니다.
단, 웜업 중에는 DB, 캐시 등 실제 원격 서비스와의 커넥션이 열린 상태이므로, 체크포인트 생성 전에 이를 처리할 수 있는 인프라 환경(예: canary 배포 환경)이 전제됩니다.
# 1. 체크포인트 저장 경로 지정 후 앱 기동
java -XX:CRaCCheckpointTo=/cr -jar app.jar
# 2. 웜업 트래픽 발생 (실제 요청 처리)
curl http://localhost:8080/owners
curl http://localhost:8080/vets
# 3. 체크포인트 생성 (PID 또는 메인 JAR명으로 지정)
jcmd app.jar JDK.checkpoint
# → 앱 프로세스 종료 (정상 동작)
# 4. 이미지로부터 복원
java -XX:CRaCRestoreFrom=/cr
복원 후 로그:
Restarting Spring-managed lifecycle beans after JVM restore
Tomcat started on port 8080 (http) with context path ''
Spring-managed lifecycle restart completed (restored JVM running for 50 ms)
모드 2: Automatic 체크포인트 (자동)
JVM 시스템 프로퍼티 하나로 기동 중 특정 시점에 자동으로 체크포인트를 생성합니다. jcmd를 별도로 실행할 필요 없어 CI/CD 파이프라인 통합에 유리합니다.
java -Dspring.context.checkpoint=onRefresh \
-XX:CRaCCheckpointTo=/cr \
-jar app.jar
# → ApplicationContext refresh 완료 시점에 자동 체크포인트 후 종료
체크포인트가 생성되는 정확한 시점은 다음과 같습니다.

즉, 빈 초기화는 완료됐지만 Tomcat 포트 바인딩과 스케줄러 시작은 아직 이루어지지 않은 상태입니다. 초기화 비용만 제거하는 방식이므로, JIT 웜업 효과는 On-demand 방식보다 적습니다.
두 모드 비교

개발 환경에서 동작 검증하기
CRaC JDK가 없는 Mac/Windows 환경에서도 체크포인트 시점의 동작을 검증할 수 있는 방법이 두 가지 있습니다.
방법 1: spring.context.exit=onRefresh (Spring 제공)
체크포인트는 생성하지 않고, 해당 시점에 원격 서비스 커넥션이 발생하는지 확인합니다. CRaC JDK가 전혀 없어도 동작합니다.
java -Dspring.context.exit=onRefresh -jar app.jar
# → onRefresh 시점에 앱 종료 (체크포인트는 생성하지 않음)
방법 2: simengine (Azul Zulu 전용)
1편에서 설명한 simengine은 체크포인트 직후 동일 프로세스 내에서 즉시 복원까지 수행합니다.
Linux/Windows/macOS 모두 지원하며, beforeCheckpoint → afterRestore 콜백이 실제로 순서대로 호출되는지 확인할 수 있어 방법 1보다 더 정확한 검증이 가능합니다. Azul Zulu CRaC JDK가 있는 경우 활용합니다.
# Azul Zulu CRaC JDK 사용 시
java -XX:CRaCEngine=simengine \
-XX:CRaCCheckpointTo=/cr \
-jar app.jar
# → beforeCheckpoint → afterRestore 콜백 순서대로 실행 후 앱 계속 동작
3. 기동 시간 측정 방법
CRaC 적용 효과를 측정할 때는 측정 기준을 명확히 해야 합니다.
측정 기준 3가지
| 기준 | 측정 방법 | 특징 |
| Spring 로그 파싱 | Started in N seconds grep |
빠르지만 복원 시 수치 왜곡 가능 |
| 복원 전용 로그 | restored JVM running for N ms grep |
CRaC 복원 시 정확 |
| HTTP 첫 응답까지 | 외부 폴링 wall-clock | 가장 정확, 공식 벤치마크 표준 |
왜 Spring 로그는 왜곡되는가?
Started PetClinicApplication in 330.329 seconds처럼 비정상적으로 큰 수치가 나오는 경우가 있습니다. 이는 체크포인트 당시의 시작 시각을 기준으로 경과 시간을 계산하기 때문에, 체크포인트와 복원 사이의 대기 시간이 그대로 더해지기 때문입니다. Azul을 포함한 대부분의 공식 벤치마크는 HTTP 첫 응답까지의 wall-clock time을 기준으로 삼습니다.
측정 스크립트 예시
#!/bin/bash
# 기동 시간 측정: HTTP 첫 응답까지 wall-clock
START=$(date +%s%3N)
java -XX:CRaCRestoreFrom=/cr &
until curl -sf http://localhost:8080/actuator/health > /dev/null 2>&1; do
sleep 0.005 # 5ms 간격 폴링
done
END=$(date +%s%3N)
echo "복원 완료까지: $((END - START)) ms"
반복 측정 및 통계
1회 측정은 노이즈가 많습니다. 최소 30~50회 반복 후 P95(95th percentile) 기준으로 비교하는 것이 일반적입니다.
측정 횟수: 50
최솟값: 43 ms
평균: 51 ms
P95: 67 ms ← 이 수치를 기준으로 비교
4. 주요 주의사항
@Scheduled fixedRate 문제
On-demand 방식으로 체크포인트를 생성하면, 복원 시 체크포인트~복원 사이에 실행되지 못한 fixedRate 작업이 한꺼번에 몰려 실행됩니다. Spring Framework 공식 문서에서도 이를 명시적으로 경고하며, fixedDelay 또는 cron으로의 변경을 권장합니다.
// ❌ 위험: 체크포인트 중 누락된 실행이 복원 시 한꺼번에 수행됨
@Scheduled(fixedRate = 5000)
public void syncData() { ... }
// ✅ 안전: 이전 실행 완료 후 5초 대기
@Scheduled(fixedDelay = 5000)
public void syncData() { ... }
// ✅ 안전: cron은 실행 이후 시점 기준으로 다음 실행 계산
@Scheduled(cron = "*/5 * * * * *")
public void syncData() { ... }
Hikari allow-pool-suspension
체크포인트 시 다음 경고가 발생할 수 있습니다.
WARN: HikariDataSource is not configured to allow pool suspension.
This will cause problems when the application is checkpointed.
application.properties에 다음을 추가합니다.
spring.datasource.hikari.allow-pool-suspension=true
체크포인트 이미지 보안
체크포인트 이미지는 체크포인트 시점 JVM이 처리한 모든 값의 스냅샷입니다. 환경변수, 설정 프로퍼티, API 키, DB 패스워드가 평문으로 파일에 저장될 수 있습니다.
컨테이너 이미지에 체크포인트 파일을 포함시킬 경우 특히 주의가 필요합니다.
✅ 권장: 체크포인트 시점에 시크릿을 아직 주입하지 않은 상태
✅ 권장: 이미지 파일 접근 권한을 엄격하게 제한
❌ 위험: DB 패스워드가 주입된 상태에서 퍼블릭 컨테이너 레지스트리에 이미지 push
5. Petclinic 기준 실습 흐름
환경
- CRaC 지원 JDK (Azul Zulu 21 또는 BellSoft Liberica 21)
- Spring Boot 3.2+ Petclinic
- Linux (Ubuntu 24.10+의 경우
openjdk-21-crac-jdk-headless패키지로 설치 가능)
전체 흐름 — CRIU 엔진 (기본)
# 1. CRaC JDK JAVA_HOME 설정
export JAVA_HOME=/path/to/crac-jdk
# 2. 빌드
./mvnw package -q
# 3. 체크포인트 경로 지정 후 기동 (최초 1회)
$JAVA_HOME/bin/java -XX:CRaCCheckpointTo=/cr -jar target/app.jar
# → Started PetClinicApplication in 4.657 seconds
# 4. (다른 터미널) 웜업 요청 발생
curl http://localhost:8080/owners
curl http://localhost:8080/vets
# 5. 체크포인트 생성
jcmd target/app.jar JDK.checkpoint
# → 프로세스 종료
# 6. 이후 실행: 이미지 복원
$JAVA_HOME/bin/java -XX:CRaCRestoreFrom=/cr
# → Spring-managed lifecycle restart completed (restored JVM running for 50 ms)
약 4.6초 → 50ms, 약 90배 개선입니다.
BellSoft 공식 튜토리얼(2024년 11월 기준)에서도 동일한 Petclinic 기준으로 5.2초 → 50ms, 약 100배 개선을 확인했습니다.
Warp 엔진 사용 시 (Azul Zulu 전용)
1편에서 설명한 것처럼 Warp 엔진은 추가 권한 없이 체크포인트를 생성할 수 있어 Docker 멀티스테이지 빌드에서도 사용 가능합니다. -XX:CRaCEngine=warp만 추가하면 됩니다.
$JAVA_HOME/bin/java \
-XX:CRaCEngine=warp \
-XX:CRaCCheckpointTo=/cr \
-jar target/app.jar
체크포인트 이미지를 다른 머신(CPU 마이크로아키텍처가 다를 수 있는 컨테이너 환경 등)에서 복원할 계획이라면 -XX:CPUFeatures=generic 옵션을 함께 지정합니다.
$JAVA_HOME/bin/java \
-XX:CRaCEngine=warp \
-XX:CPUFeatures=generic \
-XX:CRaCCheckpointTo=/cr \
-jar target/app.jar
Warp 엔진은 복원 시 PID/TID가 변경됩니다. PID에 직접 의존하는 코드가 있다면
Resource의afterRestore에서 처리해야 합니다.
6. 프레임워크별 지원 현황
CRaC를 지원하는 프레임워크는 Spring Boot 외에도 있습니다.
| 프레임워크 | 지원 버전 | 특이사항 |
| Spring Boot | 3.2+ | org.crac 의존성 추가만으로 동작 |
| Quarkus | 2.10.0+ | 기본 내장 지원 |
| Micronaut | — | crac 피처 추가. Hikari·Redis 조율 내장 |
| AWS Lambda SnapStart | — | CRaC와 유사한 메커니즘. Lambda 전용 |
정리
| 항목 | 내용 |
| 지원 버전 | Spring Boot 3.2+ / Spring Framework 6.1+ |
| 의존성 | org.crac:crac:1.5.0 추가만으로 대부분 동작 |
| 체크포인트 모드 | On-demand (jcmd) / Automatic (onRefresh) |
| 측정 기준 | HTTP 첫 응답까지 wall-clock이 가장 정확 |
| @Scheduled 주의 | fixedRate → fixedDelay 또는 cron으로 변경 |
| Hikari 설정 | allow-pool-suspension=true 추가 |
| 보안 | 이미지에 시크릿 포함 위험 반드시 인지 필요 |
다음 편에서는 CRaC를 지원하는 JDK 배포판을 비교하고, 상황별 선택 가이드를 정리합니다.
참고 문서
'CS > JVM' 카테고리의 다른 글
| [JVM] CRaC 완전 정복 (3편) - JDK 배포판 비교와 실무 선택 가이드 (0) | 2026.06.21 |
|---|---|
| [JVM] CRaC 완전 정복 (1편) - 개념과 동작 원리 (0) | 2026.06.21 |
| [JVM] Project Leyden과 AOT Cache - Java가 콜드 스타트 문제를 해결하는 방법 (0) | 2026.06.21 |
| [JVM] CDS와 AppCDS - JVM이 클래스 로딩을 캐싱하는 방법 (0) | 2026.06.20 |
| [JVM] invokevirtual과 invokeinterface — 바이트코드부터 vtable/itable까지 (1) | 2026.05.18 |