반대로 JVM 계열 언어를 일반적으로 컴파일해서 사용할 순 없나요?
가능합니다. JVM 언어도 바이트코드 대신 네이티브 기계어로 직접 컴파일(AOT) 해서 JVM 없이 실행할 수 있으며, 대표적인 도구가 GraalVM Native Image입니다.
기본 흐름 vs AOT 컴파일
[ 기본 — JVM 위에서 실행 ]
Hello.java → javac → Hello.class → JVM(Interpreter + JIT) → OS/CPU
↑ JVM 필요
[ AOT — 네이티브 바이너리로 변환 ]
Hello.java → javac → Hello.class → native-image → hello (실행파일) → OS/CPU 직접 실행
↑ GraalVM AOT ↑ JVM 불필요
대표 도구: GraalVM Native Image
# 1. 일반적인 Java 컴파일
javac Hello.java
# 2. AOT 컴파일로 네이티브 바이너리 생성
native-image Hello
# 3. JVM 없이 직접 실행
./hello
| 항목 | JVM 실행 | Native Image |
| 시작 속도 | 수백 ms ~ 수 초 | 수 ms ~ 수십 ms |
| 메모리 사용 | 많음 (JVM 오버헤드) | 적음 |
| 최대 성능 | JIT 워밍업 후 매우 빠름 | 정적 최적화 수준 |
| 배포 크기 | JRE 포함 시 ~200MB | 바이너리 ~20~50MB |
| JVM 필요 여부 | 필요 | 불필요 |
다른 AOT 도구들
| 도구 | 대상 언어 | 특징 |
| GraalVM Native Image | Java, Kotlin, Scala 등 | 가장 대표적, Spring Boot 공식 지원 |
| Kotlin/Native | Kotlin | LLVM 기반, iOS·임베디드 타겟 가능 |
| Scala Native | Scala | LLVM 기반 네이티브 컴파일 |
Kotlin/Native 예시
// hello.kt
fun main() {
println("JVM 없이 실행!")
}
kotlinc-native hello.kt -o hello
./hello.kexe # JVM 없이 직접 실행
Kotlin 소스
│
├── kotlinc ────→ .class ──→ JVM 실행 (기본)
│
└── kotlinc-native → LLVM IR → 기계어 바이너리 (AOT)
↑ JVM 불필요
AOT의 한계
만능은 아니며, 트레이드오프가 존재합니다.
| 한계 | 설명 |
| 리플렉션 제한 | 런타임에 클래스 정보를 동적으로 탐색하는 기능이 제한됨 → 별도 설정 필요 |
| 동적 프록시 제한 | Spring AOP 등에서 사용하는 동적 프록시 일부 불가 |
| 빌드 시간 증가 | AOT 컴파일 자체가 수 분 이상 소요 |
| JIT 최적화 부재 | 런타임 프로파일링 기반 최적화가 없으므로 장시간 실행 시 JVM이 더 빠를 수 있음 |
| 디버깅 어려움 | 네이티브 바이너리는 JVM 도구(jstack, jconsole 등) 사용 불가 |
성능
▲
│ ┌── JVM (JIT 워밍업 후)
│ ╱
│ ╱
│ ─────╱──── Native Image (일정한 성능)
│ ╱
│ ╱ ← JVM 초기 워밍업 구간
└──────────────────────────► 시간
짧은 실행: Native Image 유리 (서버리스, CLI)
긴 실행: JVM 유리 (장기 운영 서버)
실무 적용 사례
| 사례 | 선택 | 이유 |
| AWS Lambda (서버리스) | Native Image | 콜드 스타트 시간 최소화 |
| CLI 도구 | Native Image | 즉시 시작, 배포 간편 |
| 마이크로서비스 | Native Image | 컨테이너 이미지 경량화 |
| 대규모 장기 운영 서버 | JVM | JIT 최적화로 장기 성능 우수 |
Spring Boot 3 + GraalVM 예시
# Spring Boot에서 네이티브 빌드
./gradlew nativeCompile
# 결과: JVM 없이 실행 가능한 바이너리
./build/native/nativeCompile/my-app
# 시작 시간 비교
# JVM: ~2.5초
# Native Image: ~0.05초 (약 50배 빠름)
핵심 정리
JVM 언어도 GraalVM Native Image, Kotlin/Native 등을 통해 네이티브 바이너리로 AOT 컴파일할 수 있습니다. 이 경우 JVM 없이 실행 가능하고 시작 속도·메모리에서 큰 이점이 있지만, 리플렉션 제한과 JIT 최적화 부재라는 트레이드오프가 있습니다. 따라서 서버리스·CLI처럼 빠른 시작이 중요한 환경에서는 AOT, 장기 운영 서버에서는 JVM이 여전히 유리합니다.