GraalVM

2024. 10. 25. 09:46자바

카카오 컨퍼런스 2024에서 GraalVM에 대한 세션을 들어서 해당 내용에 대해서 이해하기 위해 공부한 내용을 적겠습니다.

Interpreter, JIT, AOT 등에 대한 개념이 나오므로 아직 모르신다면 이 글을 읽어주세요.


HotspotVM

기본적으로 JVM은 HotspotVM으로 구동됩니다.

 

HotspotVM은 구동 후 웜업이 되었을 때의 성능은 훌륭하나 JVM이 막 구동되고 웜업 되기 전까지의 성능이 좋지 않다는 특징이 있는데, 이는 HotspotVM이 JIT 컴파일 방식을 사용하기 때문입니다.


자바의 컴파일 방식

우리가 코드를 작성하면 .java라는 파일로 저장이 됩니다.

이걸 자바 컴파일러가 .class 라는 중간 단계 언어인 바이트코드로 컴파일하고 JVM을 통해서 .class 파일을 실행시키는데,

이 과정에서 JVM은 바이트코드를 컴퓨터가 인식할 수 없으므로 네이티브 언어로 번역해서 실행을 합니다.

 

JIT 컴파일

JVM은 실행 중 특정 메서드나 루프가 자주 호출되는 것을 감지하는데, 이렇게 자주 호출되는 메서드나 루프를 네이티브 언어로 미리 해석해 놓고 캐시를 해놓습니다.

이러한 JIT 컴파일 방식을 통해 웜업 후에는 좋은 성능을 보이지만 여전히 캐시되기 전까지의 성능은 보장이 되지 않는 단점이 있습니다.

 

카카오 세션에서는 블루그린 방식으로 배포를 할 때에 대해서 설명을 해주었는데,

초기 구동 문제를 해결하기 위해서 트래픽을 로드밸런서로 처음에는 블루 쪽에 99% 그린에 1% 이렇게 분배를 해주고,

비율을 점점 바꿔가면서 새로 배포되어 실행된 JVM에 대해서 웜업을 해주는 방식을 사용한다고 했습니다.

 

이 방식은 스위칭을 해줄 때 CPU의 사용량이 높아진다라는 단점과 배포시마다 로드밸런서에 대한 관리 등과 같이

배포과정이 복잡해진다는 단점이 있다고,

극단적으로 모든 서버가 다 내려가는 상황이 발생했을 때 다시 서버를 띄울탠데, 막 띄워진 서버가 

몰아치는 트래픽을 소화하기 어려울 수 있다는 문제점도 있다.

(= 서버 노드의 수를 늘려 초기 트래픽 문제를 해결해도 웜업이 된 후에는 노는 자원이 생긴다.)

 

세션에서는 GraalVM을 사용하여 이 문제를 해결하였다고 소개하였습니다.

 


GraalVM

GraalVM은 AOT 컴파일 방식을 사용합니다.

 

AOT에 대해서

AOT는 컴파일 타임에 모든 코드와 의존성을 분석해 네이티브 코드(=기계어)로 컴파일을 해두는 방식을 말합니다.

 

JIT 컴파일의 경우에는 앞서 말했듯 애플리케이션이 실행되면서 바이트코드를 네이티브 언어로 컴파일하는데,

이 과정에서 프로파일링을 통해 실행환경에 대한 정보를 얻고 그 정보를 바탕으로 코드를 최적화합니다.

반면에 AOT에서는 미리 특정환경에 맞게 컴파일을 진행하므로 컴파일과 동시에 최적화가 진행되므로 실행환경에 대한 정보 없이 최적화가 진행됩니다.

 

GraalVM의 한계점

AOT는 미리 기계어로 컴파일을 하는 과정에서 실행될 가능성이 있는 모든 클래스, 메서드 필드를  미리 알고 있어야 합니다.

이러한 특성으로 인해서 JVM에서 동적으로 동작하는 기능들을 사용할 수 없게 합니다.

Java Reflection, Dynamic Proxy 등등

 

Java Reflection

Class<?> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.newInstance();

 

AOT는 이 클래스를 어떤 클래스가 리플렉션으로 호출될지 컴파일 타임에 알 수 없기 때문에 해당 클래스를 네이티브 코드에 포함시켜야 하는지를 판단할 수 없습니다.

 

Dynamic Proxy

MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
    classLoader, new Class[]{MyInterface.class}, invocationHandler);

 

Proxy 클래스는 런타임 중에 JVM이 바이트코드를 생성합니다.

AOT는 미리 컴파일된 코드만 실행하므로 런타임 바이트코드 생성이 불가능합니다.


해결 방법

위에서 언급한 문제로 인해 사실상 GraalVM을 사용하면 아무것도 할 수 없을 것 같습니다.

해당 세션에서는 이러한 문제를 해결하기 위한 방법도 설명해 줬습니다.

 

Reachability Metadata

GraalVM Native에서는 동적 기능을 위해 필요할 것으로 기대되는 의존성들 전부를 컴파일 타임에 목록으로 제공할 수 있는데,

이를 Reachability Metadata라고 합니다.

 

Tracing Agent

Reachability Metadata를 사람이 직접 관리하는 건 사실상 어려운 부분이기 때문에 이를 해결하기 위해

Tracing Agent가 제공됩니다.

 

Tracing Agent가 적용된 상태로 Hotspot JVM을 앱이 실행되는 동안 사용된 각종 동적 기능들을 관측하고 런타임에 필요할 것으로 생각되는 모든 비명시적 의존성을 추적해서 Reachability Metadata를 만들어줍니다.

 

결국, 앱이 정상적으로 동작하기 위해선 Tracing Agent를 이용해 완전한 Reachability Metadata를 만들어야 합니다.

세션에서 제공한 방식은 아래의 2가지입니다.

  • Tracing Agent를 적용한 상태로, 앱을 기동 - 종료
  • Tracing Agent를 적용한 상태로, 커버리지가 높은 테스트 프로그램을 실행

이러한 과정을 빌드과정에 포함시켜 완전한 Reachability Metadata를 획득해 컴파일에 사용합니다.


벤치마크 결과

  • 앱 초기화 시간
    3.76s(Hotspot JVM)에서 0.103s(GraalVM Native)로 줄었음
  • 앱 응답시간
    Hotspot JVM: 기동 직후에는 1초 이상이 걸리고 웜업 후에 최적화된 응답 시간
    GraalVM: 기동 직후부터 최적화된 응답 시간
    단, 최종적으로 도달하는 응답 시간은 Hotspot JVM이 좋음
  • 앱 처리량
    Hotspot JVM: 기동 직후에는 낮은 처리량, 웜업후에 좋은 성능에 도달
    GraalVM: 기동 직후부터 괜찮은 성능에 도달
    단, Hotspot JVM은 런타임 정보를 기반으로 최적화하기 때문에 최종 성능에서 더 좋은 결과가 나옴
    (세션에서는 완전히 웜업이 끝난 상태에서는 Hotspot JVM이 25% 정도 더 좋은 성능을 보인다고 설명)

해당 세션에서는 최종 웜업시에 성능차이가 심한 것을 완화하기 위해서 AOT에 런타임 프로파일 정보를 넣어주어

성능 차이를 8%까지 줄였다고 설명하였습니다.

 

배포 충격

마지막으로 앱을 배포하고 트래픽의 완충시간 없이 즉시 스위칭 했을 때의 배포 충격을 비교했을 때 아래와 같습니다.

  • Hotspot: CPU 사용량이 급격히 늘어나는 스파이크가 발생함.
  • Graal: CPU 사용량에 문제가 없음.

배포 충격이 없다 보니 배포 시에 즉시 스위칭이 가능하다는 장점이 있어 배포과정이 단순화될 수 있습니다.

'자바' 카테고리의 다른 글

Exception  (0) 2025.01.13
Java Reflection  (0) 2024.10.25
빌드(Build)  (0) 2024.06.17
Optional  (0) 2024.04.24
[JVM] Class Loader  (0) 2024.04.18