Spring Boot 3부터, GraalVM Native Image를 공식 지원하여 애플리케이션의 시작 속도와 메모리 사용량을 크게 줄일 수 있다.
Native Image란 기존의 JVM 기반 위에서 돌아가는 Java 애플리케이션과는 달리 JVM 없이 즉시 실행할 수 있는 바이너리 파일이다. (Native Java)
Kotlin언어로 SpringBoot 애플리케이션을 만들고, 이를 Native Image로 컴파일하여 실행하는 예제를 구현해본다.
먼저 간단하게 Kotlin으로 SpringBoot 3.x 버전으로 프로젝트를 만든다. 예제코드에는 h2, MySQL DB와의 접속 설정과 간단하게 JPA로 구현한 Create, Read 기능을 수행하는 API Endpoint 2개를 넣었다.
그리고 GraalVM JDK를 설치한다.
- 설치 URL : https://www.graalvm.org/downloads/#
- 가이드 : https://www.graalvm.org/latest/getting-started/macos/
설치 가이드에서는 sdk man이라는 유틸리티를 사용 권장하는데 꼭 쓸 필요는 없다. JVM 버전 스위칭하기에는 편리한 프로그램이다.
GraalVM에는 Community Edition (CE) 버전과 Enterprise Edition (EE) 버전 2개의 버전이 있는데 대부분의 경우는 꼭! CE 버전을 설치하여야 한다. EE 버전을 쓰다가 걸리면 오라클에서 돈 내놓으라고 찾아올 수도 있다.
MacOS에서는 native-image 도구를 사용하기 위해 xcode를 사용한다고 한다.
![]() |
| 터미널에서 'xcode-select --install' 커맨드 실행 |
build.gradle.kts 에서 plugins 에 graavm의 native 관련 plugin을 추가한다.
![]() |
| IntelliJ IDEA에서 실행할 거면 이렇게 Task별로 Configuration에서 별도로 넣어줄 수 있다. |
GRAALVM_HOME 환경변수가 설정되어 있지 않다면, Native Compile을 시도할 경우에 아래와 같은 에러가 발생하고 더 이상 수행되지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | ======================================================================================================================== GraalVM Native Image: Generating 'native-image' (executable)... ======================================================================================================================== For detailed information and explanations on the build output, visit: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md ------------------------------------------------------------------------------------------------------------------------ [1/8] Initializing... (24.2s @ 0.35GB) Java version: 21.0.5+9-LTS, vendor version: Oracle GraalVM 21.0.5+9.1 Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: ML-inferred C compiler: cc (apple, x86_64, 14.0.3) Garbage collector: Serial GC (max heap size: 80% of RAM) 3 user-specific feature(s): - com.oracle.svm.thirdparty.gson.GsonFeature - org.eclipse.angus.activation.nativeimage.AngusActivationFeature - org.springframework.aot.nativex.feature.PreComputeFieldFeature ------------------------------------------------------------------------------------------------------------------------ 3 experimental option(s) unlocked: - '-H:ServiceLoaderFeatureExcludeServices' (origin(s): 'META-INF/native-image/org.springframework/spring-orm/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.springframework/spring-orm/6.2.0/e02bdeb64ee3986d0e8cf0b53dbb4ca89d25f606/spring-orm-6.2.0.jar') - '-H:ResourceConfigurationResources' (origin(s): 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-websocket/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.33/67feeb848bf741029ed7a0fe91bcae515e7b1c25/tomcat-embed-websocket-10.1.33.jar', 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/10.1.33/d4d10b953164db057e4a803506e4552731e5a6a0/tomcat-embed-core-10.1.33.jar', 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-el/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/10.1.33/6cdf9afcb8577309fae1181e0a5f2f8cff559d97/tomcat-embed-el-10.1.33.jar') - '-H:ReflectionConfigurationResources' (origin(s): 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-websocket/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.33/67feeb848bf741029ed7a0fe91bcae515e7b1c25/tomcat-embed-websocket-10.1.33.jar', 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/10.1.33/d4d10b953164db057e4a803506e4552731e5a6a0/tomcat-embed-core-10.1.33.jar', 'META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-el/native-image.properties' in 'file:///Users/walter/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/10.1.33/6cdf9afcb8577309fae1181e0a5f2f8cff559d97/tomcat-embed-el-10.1.33.jar') ------------------------------------------------------------------------------------------------------------------------ Build resources: - 12.09GB of memory (75.6% of 16.00GB system memory, determined at start) - 8 thread(s) (100.0% of 8 available processor(s), determined at start) SLF4J(W): No SLF4J providers were found. SLF4J(W): Defaulting to no-operation (NOP) logger implementation SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details. [2/8] Performing analysis... [******] (210.7s @ 3.72GB) 39,037 reachable types (91.9% of 42,491 total) 60,717 reachable fields (59.7% of 101,763 total) 213,249 reachable methods (64.1% of 332,639 total) 11,854 types, 991 fields, and 12,314 methods registered for reflection 89 types, 87 fields, and 66 methods registered for JNI access 5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z [3/8] Building universe... (67.9s @ 4.38GB) [4/8] Parsing methods... [********] (67.4s @ 2.82GB) [5/8] Inlining methods... [****] (7.3s @ 4.42GB) [6/8] Compiling methods... [*******************] (374.9s @ 4.45GB) [7/8] Laying out methods... [******] (40.6s @ 6.98GB) [8/8] Creating image... [*****] (27.9s @ 5.67GB) 104.75MB (58.24%) for code area: 125,301 compilation units 74.16MB (41.23%) for image heap: 789,483 objects and 337 resources 965.87kB ( 0.52%) for other data 179.86MB in total ------------------------------------------------------------------------------------------------------------------------ Top 10 origins of code area: Top 10 object types in image heap: 18.95MB hibernate-core-6.6.2.Final.jar 30.98MB byte[] for code metadata 17.56MB java.base 13.09MB byte[] for java.lang.String 7.98MB svm.jar (Native Image) 7.57MB java.lang.Class 7.48MB h2-2.3.232.jar 6.15MB java.lang.String 5.16MB tomcat-embed-core-10.1.33.jar 2.73MB byte[] for reflection metadata 4.51MB kotlin-reflect-1.9.25.jar 2.46MB byte[] for embedded resources 4.35MB java.xml 1.79MB com.oracle.svm.core.hub.DynamicHubCompanion 3.77MB java.desktop 1.13MB byte[] for general heap data 2.83MB mysql-connector-j-9.1.0.jar 1.08MB c.o.svm.core.hub.DynamicHub$ReflectionMetadata 2.49MB jackson-databind-2.18.1.jar 849.87kB java.lang.String[] 28.79MB for 150 more packages 6.35MB for 6946 more object types Use '-H:+BuildReport' to create a report with more details. ------------------------------------------------------------------------------------------------------------------------ Security report: - Binary includes Java deserialization. - Use '--enable-sbom' to embed a Software Bill of Materials (SBOM) in the binary. ------------------------------------------------------------------------------------------------------------------------ Recommendations: PGO: Use Profile-Guided Optimizations ('--pgo') for improved throughput. INIT: Adopt '--strict-image-heap' to prepare for the next GraalVM release. AWT: Use the tracing agent to collect metadata for AWT. HEAP: Set max heap for improved and more predictable memory usage. CPU: Enable more CPU features with '-march=native' for improved performance. ------------------------------------------------------------------------------------------------------------------------ 139.5s (16.8% of total time) in 241 GCs | Peak RSS: 7.16GB | CPU load: 5.40 ------------------------------------------------------------------------------------------------------------------------ Produced artifacts: /Users/walter/Projects/native-image/build/native/nativeCompile/native-image (executable) ======================================================================================================================== Finished generating 'native-image' in 13m 47s. [native-image-plugin] Native Image written to: /Users/walter/Projects/native-image/build/native/nativeCompile BUILD SUCCESSFUL in 13m 53s 10 actionable tasks: 2 executed, 8 up-to-date Watched directory hierarchies: [/Users/walter/Projects/native-image] 7:06:08 PM: Execution finished 'nativeCompile --info'. | cs |
컴파일 시간은 jar파일을 컴파일하는데 비해 대단히 오래 걸린다.
이 정도 볼륨의 프로젝트를 컴파일하는데 걸리는 시간은 12~13분.
컴파일이 완료되면 프로젝트의 build/native/nativeCompile 경로에 바이너리 파일이 생성된 것을 확인할 수 있고, 해당 파일을 실행하면 애플리케이션이 구동된다.
위는 기존의 JVM 위에서 jar파일로 컴파일하여 애플리케이션을 실행했을 때의 로그다. 컨텍스트가 초기화되기까지 약 1.3초, 애플리케이션 서비스가 시작되기까지 4.7초 정도가 걸렸다.
이것은 Native Image로 컴파일한 애플리케이션을 실행한 로그인데, 컨텍스트가 초기화되기까지 0.048초, 애플리케이션 서비스가 시작되기까지 0.302초가 걸렸다.
실제 운영 프로그램에 적용하면 물론 달라지겠지만 이 예제의 경우 대략 93.5%의 속도 개선이 확인된다. (흠... 광팔기 정말 좋은 현상이다)
물론 실제 운영에 적용하기에는 몇가지 취약점이 있어, 좀 더 검토가 필요할 것 같다.
- 컴파일 시간이 너무 오래 걸린다.
- 컴파일 환경과 실제 운영 환경이 완전 동일해야 안심할 수 있을 것 같다.
- Native Image를 지원하지 않는 라이브러리가 있다고 한다.
- 운영 레퍼런스가 아직 절대 부족.







