# M1에서 GraalVM으로 네이티브 이미지 만들기

기존의 JIT 컴파일러는 `.java -> javac -> 바이트코드 -> jvm -> 인터프리터 -> JIT` 순서로 실행되는데, AOT 컴파일러는 `.java -> AOT magic -> 네이티브 실행 파일` 순서로 실행됩니다.(JIT와 반대로 빌드 시 정적 코드 분석을 진행합니다.)

이후 특정 플랫폼(Windows, Mac, Linux, x64, ARM 등등)에 대한 기본 실행 파일을 생성합니다. 이는 프로그램을 시작한 후에 바이트코드 해석/컴파일을 수행할 필요가 없고 애플리케이션을 최대 속도로 시작할 수 있다는 것을 의미합니다.

이 글에서는 실행 파일로서 도커 이미지를 생성하고 실행해 봅니다.

### 💡설치

[https://github.com/graalvm/homebrew-tap](https://github.com/graalvm/homebrew-tap)

Homebrew를 통해 간편하게 설치하실 수 있습니다. 엔터프라이즈 에디션과 커뮤니티 에디션이 있는데, [엔터프라이즈 에디션이 메모리 관리가 더 우수하므로](https://www.oracle.com/a/ocom/docs/graalvm_enterprise_community_comparison_2021.pdf) EE를 설치합니다.

```bash
$ brew install --cask graalvm/tap/graalvm-jdk17
```

다운로드를 받으신 후, GRAALVM\_HOME을 설정해주시면 됩니다. 저는 zsh를 쓰고있기 때문에 .zshrc에 환경변수를 등록했습니다.

```bash
$ vi ~/.zshrc
...
export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-XX.Y.Z/Contents/Home
export JAVA_HOME=$GRAALVM_HOME
```

macOS Catalina에서는 "the developer cannot be verified" 경고 문구가 나타날 수 있습니다. 다음 커맨드를 실행하여 이를 무시할 수 있습니다.

```bash
xattr -r -d com.apple.quarantine /Library/Java/JavaVirtualMachines/graalvm-jdk-XX.Y.Z
```

### 💡빌드

Spring Initializr에서 GraalVM Native Support, Spring Web 2개의 의존성을 추가하여 메이븐 프로젝트를 생성합니다. 그 후 빌드팩 이미지를 dashaun/builder:tiny로 변경해줘야 합니다. (기본 Packeto Buildpacks 이미지는 [아직 M1 아키텍처를 지원하지 않습니다.](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-with-GraalVM#building-container-images))

```bash
<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <image>
                    <builder>dashaun/builder:tiny</builder>
                </image>
            </configuration>
        </plugin>
    </plugins>
</build>
```

이제 `$ mvn -Pnative spring-boot:build-image` 명령어로 애플리케이션을 정적 컴파일하여 이미지를 빌드할 수 있습니다. (Docker가 실행 중이어야 합니다.)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688280686237/e29d2a38-bc0d-45d9-8b56-a7392da252cb.png align="center")

로그를 살펴보면 미리 스프링을 가동시키고 이미지를 빌드하는 것을 확인할 수 있는데요, 이 빌드 시간이 굉장히 오래 걸립니다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688281736337/107d0699-580d-4011-ae58-81a0fb35fe2a.png align="center")

드디어 docker.io/library/demo:0.0.1-SNAPSHOT 이미지가 생성됐습니다! 제 맥북(M1 Air 16GB)에서 22분 49초가 걸리네요.

어쨌거나 빌드 시간이 보람 있어야겠죠. 이 네이티브 이미지는 얼마나 빨리 구동될까요?

우선 기존의 방식대로 JAR 파일로 실행해 봅시다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688287111263/d88d7f9d-49ff-41ae-a766-746c2b76d530.png align="center")

애플리케이션 실행까지 3.358초가 걸리네요. 그럼 이제 다음과 같이 네이티브 이미지를 실행해봅시다.

`$ docker run --rm -p 8080:8080 docker.io/library/demo:0.0.1-SNAPSHOT`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688286212588/fdc265ed-e74b-4508-8214-28e0609fde34.png align="center")

이전보다 5배 이상 적은 0.647초가 걸렸네요.

빌드를 미리 해두었기 때문에 실행 과정에서 불필요한 과정들을 모두 생략해버렸습니다.

---

### 🎯정리

* 기존에 JVM을 사용하고 있다면, JIT 컴파일러의 한 종류로서의 GraalVM 선택을 고려해 봅시다. [단순히 GraalVM 컴파일러로만 바꿔줘도 성능이 좋아집니다.](https://martijndwars.nl/2020/02/24/graal-vs-c2.html)
    
* 네이티브 이미지는 AOT 컴파일러이며, 이를 쓰면 더 빠르게 애플리케이션을 실행할 수 있습니다.
    

---

### 🔖참고

* [https://www.graalvm.org/latest/reference-manual/native-image/](https://www.graalvm.org/latest/reference-manual/native-image/)
    
* [https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html](https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html)
    
* [https://giljae.medium.com/graalvm-%EC%86%8C%EA%B0%9C-84ac547f8df2](https://giljae.medium.com/graalvm-%EC%86%8C%EA%B0%9C-84ac547f8df2)
    
* [https://www.marcobehler.com/guides/graalvm-aot-jit](https://www.marcobehler.com/guides/graalvm-aot-jit)
