JIT & AOT 컴파일
Q. JIT 컴파일과 AOT 컴파일에 대해서 설명해주세요.
JIT 컴파일과 AOT 컴파일은 프로그램의 실행 코드를 만드는 두 가지 주요한 컴파일 방식입니다. 먼저 JIT(Just-In-Time) 컴파일은 프로그램을 실행하는 시점에 기계어로 번역하는 방식입니다. Java를 예로 들면, 자바 소스코드는 먼저 바이트코드로 컴파일되고, 이 바이트코드는 프로그램이 실행될 때 JIT 컴파일러에 의해 실제 기계어로 변환됩니다. 이 방식의 장점은 실행 시점에 최적화가 가능하다는 것이고, 단점은 초기 실행 시에 약간의 오버헤드가 발생한다는 점입니다. 반면 AOT(Ahead-Of-Time) 컴파일은 프로그램을 실행하기 전에 미리 기계어로 번역하는 방식입니다. C나 C++같은 언어가 대표적인 예시인데, 컴파일 시점에 이미 실행 가능한 기계어 코드가 만들어집니다. 이 방식의 장점은 실행 시점에 별도의 컴파일 과정이 필요 없어 초기 실행 속도가 빠르다는 것이고, 단점은 실행 시점의 최적화가 어렵다는 점입니다. 최근에는 이 두 가지 방식을 혼합해서 사용하는 경우도 많은데, 안드로이드의 ART(Android Runtime)나 GraalVM의 Native Image가 그 예시라고 할 수 있습니다.
Q. 메모리 사용량 측면에서 두 방식을 비교해주실 수 있나요?
먼저 JIT 컴파일 방식의 경우, 메모리 사용량이 다소 높은 편입니다. 그 이유는 크게 세 가지로 설명할 수 있습니다. 첫째, 실행 시점에 컴파일을 수행하기 위한 JIT 컴파일러가 메모리에 상주해야 합니다. 둘째, 원본 바이트코드와 컴파일된 기계어 코드를 모두 메모리에 유지해야 하므로 이중의 메모리 공간이 필요합니다. 셋째, 프로파일링 정보와 최적화를 위한 중간 데이터 등 추가적인 메모리가 필요합니다. 반면에 AOT 컴파일 방식은 상대적으로 메모리 사용량이 적습니다. 컴파일이 이미 완료된 기계어 코드만 메모리에 로드하면 되기 때문입니다. 또한 별도의 컴파일러나 최적화 도구가 실행 시점에 필요하지 않아 그만큼 메모리를 절약할 수 있습니다. 다만, AOT 컴파일의 경우 다양한 환경을 고려해 미리 컴파일을 해야 하므로, 실행 파일의 크기가 커질 수 있다는 점은 고려해야 합니다. 하지만 이는 디스크 공간의 문제이지 실행 시 메모리 사용량과는 직접적인 관련이 없습니다.
Q. Java의 JIT 컴파일러는 어떤 방식으로 작동하나요?
Java의 JIT 컴파일러는 크게 4단계로 동작합니다. 첫째, Java 소스코드는 먼저 자바 컴파일러(javac)에 의해 바이트코드로 변환됩니다. 이 바이트코드는 플랫폼 독립적인 중간 코드입니다. 둘째, JVM이 실행되면 처음에는 인터프리터가 이 바이트코드를 한 줄씩 해석하여 실행합니다. 이때 JVM은 각 메소드의 호출 빈도와 루프 실행 횟수 등을 모니터링합니다. 셋째, 특정 코드가 자주 실행되는 것이 감지되면, JIT 컴파일러가 해당 부분을 네이티브 코드로 컴파일합니다. Java에서는 이를 위해 두 가지 컴파일러인 C1과 C2를 사용하는데, C1은 간단한 최적화를, C2는 더 복잡한 최적화를 수행합니다. 넷째, 컴파일된 네이티브 코드는 코드 캐시에 저장되어 재사용됩니다. 다음에 같은 코드가 실행될 때는 인터프리터 대신 이 최적화된 네이티브 코드가 직접 실행되므로 성능이 크게 향상됩니다. 이러한 방식을 통해 Java의 JIT 컴파일러는 프로그램의 실행 패턴을 분석하고, 자주 사용되는 코드를 선택적으로 최적화하여 전체적인 성능을 개선합니다.
Last updated