Java

Java Virtual Machine JIT Compiler

hyuk0309 2025. 12. 29. 22:41

 

이번 글에서 Java Virtual Machine에 대해 정리한다.

기본적인 개념, 동작 원리에 대해 살펴보고 그 중 성능과 관련이 큰 JVM의 Execution Engine에 대해 정리한다.

 

JDK vs JRE vs JVM

본격적인 내용에 앞서 세 가지 핵심 용어의 차이를 정리한다.

 

JDK(Java Development Kit) : 자바 프로그램을 개발, 컴파일, 디버그 및 실행을 위한 환경과 도구. JRE + 여러 개발 도구들로 구성됨.

JRE (Java Runtime Environment) : 자바 애플리케이션을 구동하기 위해 사용되는 소프트웨어 컴포넌트들의 묶음. JVM + 자바 프로그램에 필요한 Class + Property Files로 구성됨.

JVM (Java Virtual Machine) : 자바 프로그램을 실행하는 가상 머신

 

What is JVM

JVM 동작 방식

Java Virtual Machine은 자바로 개발한 프로그램이 실행되는 공간이다.

실제 java를 이용해 javac로 컴파일한 jar 파일을 실행하면 아래와 같은 단계로 프로그램이 실행된다.

 

1. 바이트 코드를 해석한다.

javac(java compiler)로 만든 .class 파일을 JVM Interpreter가 읽어 이해한다.

 

2. class 정보들을 메모리 영역에 저장한다.

1번 단계에서 이해한 Class 구조, 메서드, 변수 정보등을 JVM의 메모리 공간에 저장한다.

 

3. 바이트 코드를 실행한다.

준비된 정보를 바탕으로 실제 명령을 수행한다. 그리고 이때 반복되는 코드를 JIT 컴파일러를 통해 기계어로 직접 변환하기도 한다.

 

JVM 구성

JVM은 위에서 말한 작업들을 실행하기 위해 3개의 구성요소로 이뤄져있다.

 

Class Loader : 바이트코드를 Loading -> Verifying -> Linking 하는 작업들을 한다.

Runtime Data Areas : 자바 프로그램을 실행하는 동안 사용되는 메모리 영역.

Execution Engine : 메모리 영역에 있는 정보를 활용하여 실제 명령어를 실행한다.

 

이번 글에서는 Execution Engine을 좀 더 알아본다. JVM 관련해 더 자세한 내용을 알고 싶다면, Java Virtual Machine Specification 을 참고하면 된다. (작성일 기준 Java SE 25가 가장 최신 버전)

 

Execution Engine

Execution Engine 구성

Execution Engine은 3개의 구성요로소 이뤄져있다.

Interpreter : 바이트코드를 해석해준다.

Just-In-Time Compiler : 자주 호출되는 메서드의 바이트 코드를 네이티브 코드로 변경해주는 컴파일러이다.

Garbage Collector : 힙 메모리 사용량을 보면서, 메모리 정리가 필요하면 사용하지 않는 객체들을 메모리에서 제거한다.

 

이번글에서는 JIT(Just-In-Time Compiler)에 대해 정리한다.

 

JIT 컴파일러가 필요한 이유?

Java로 작성한 프로그램의 실행 코드는 JVM위에 바이트 코드 형태로 저장되어있다. 따라서 실제로 코드가 실행되기 위해서는 바이트 코드를 native machine code 로 번역하는 과정이 필요하다. 그리고 그 과정을 JVM의 Interpreter가 담당한다.

 

이 때문에 컴파일 시점에 바로 native machine code가 되는 C 언어 등과 비교했을 때 상대적으로 성능이 낮을 수 밖에 없다. Java는 이 러한 성능 문제를 극복하기 위해 JIT Compiler를 도입했다.

 

JIT Compiler 동작원리

(Java SE를 구현한 여러 구현체 중 OpenJDK를 기준으로 설명하겠다.)

 

Jit Compiler를 이용한 최적화 과정은 아래와 같다.

1. Interpreter가 바이트코드를 native machine code로 바꾸는 과정에서 메소드의 호출 횟수, 메소드 안에 루프의 반복 횟수를 측정한다.

2. 1번에서 측정한 값들이 임계치를 넘으면, Jit Compiler에게 컴파일 요청을 한다.

3. Jit Compiler가 바이트코드를 최적화해 native machine code로 컴파일한다.

4. 다음에 해당 메소드가 실행될때 interpreter 하는 과정없이 3번 과정에서 컴파일된 native machine code로 수행한다.

 

(동작 원리를 좀 더 자세히 이해하고 싶다면 OpenJDK 소스 코드를 참고하기 바란다.)

 

위 과정으로 자주 사용되는 코드는 native machine code로 변환해두기 때문에 성능이 더 좋아진다. 3번 과정을 좀 디테일하게 정리하겠다.

 

Three Tiers of Execution

OpenJDK의 VM은 실행에 있어 three tiers를 갖고 있다. 

1단계 : Interpreter

2단계 : C1 (quick compiler)

3단계 : C2 (optimizing compiler)

 

각 티어는 실행하기까지의 지연시간과 실행 스피드 간 트레이드 오프 관계가 있다. 1단계에 가까워질수록 실행의 지연시간은 없지만 실행 스피드가 느리다. 반대로 3단계에 가까워 질수록 실행 지연시간은 길지만, 실행 스피드는 빨라진다. 그래서 JVM은 코드가 자주 사용될 수록 점진적으로 단계를 상승시킨다.

 

처음에는 Interpreter를 이용해 코드를 실행하고. 코드가 자주 사용되면(Warm 상태) C1을 이용해 컴파일 후 컴파일된 코드를 사용한다. 그 후 코드가 매우 빈번하게 사용되면(Hot 상태) C2를 이용해 컴파일 후 컴파일된 코드를 사용하는 방식이다. 이때 단순히 카운트로만 동작하진 않고 실행 속도와 컴파일 부하 사이의 균형을 맞추면서 진행된다.

 

Deoptimization and Speculation

Tree Tiers에서 3단계에서 1단계로 가기도 한다. 이 과정을 Deoptimization 이라고 부른다.

Deoptimization이 존재하는 이유는 C2가 매우 공격적인 최적화를 수행하기 때문이다. 3단계로 넘어갈때 C2는 리스크를 감수하고 최적화를 진행한다. 그래서 최적화 코드로 인해 실행 중 오류가 발생할 수 있다. 이때 Deoptimization을 이용해 이전 단계로 돌아가고, 다음에 최적화할때 이 오류를 참고해서 최적화를 진행한다.

 

 

Demo

이론과 동일하게 동작하는지 테스트 해보겠다.

샘플코드는 아래와 같다.

public class JitDemo {
    public static void main(String[] args) {
       for (int i = 0; i < 1_000_000; i++) {
          calculate(i);
       }
    }

    private static int calculate(int n) {
       return n * n;
    }
}

 

Ref : https://github.com/hyuk0309/play-ground/tree/main/jit-compiler-demo

 

실행 결과

 

첫 번째 숫자 : JVM이 시작된 후 경과된 시간

두 번째 숫자 : 컴파일 작업 ID

세 번째 숫자 : Execution Tier (1 ~ 3 : C1, 4 : C2)

 

빨간 박스를 보면 아래처럼 진행되었음을 알 수 있다.

1. calculate 메소드가 호출 임계치를 넘어 C1 컴파일러를 이용해 컴파일 함.

2. calculate 메소드가 호출 임계치를 넘어 C2 컴파일로 최적화 함.

3. C2 컴파일이 완료되어, C1으로 컴파일된 코드는 더 이상 사용하지 않도록 폐기 처리함

 

 

마무리

이번 글에서는 JVM의 기본 개념 및 동작원리를 알아봤다. 그리고 JVM의 Execution Engine 에서 Interpreter와 JIT Compiler가 하는 역할을 좀 더 자세히 다뤘다.

 

Reference

JDK vs JRE vs JVM : https://www.baeldung.com/jvm-vs-jre-vs-jdk

What is JVM : https://www.ibm.com/think/topics/jvm-vs-jre-vs-jdk

Java SE Specification : https://download.java.net/java/early_access/loom/docs/specs/index.html

Java Virtual Machine Specification : https://docs.oracle.com/javase/specs/jvms/se25/html/index.html

openJDK 용어집 : https://openjdk.org/groups/hotspot/docs/HotSpotGlossary.html

RedHat Blod : https://developers.redhat.com/articles/2021/06/23/how-jit-compiler-boosts-java-performance-openjdk#

Oracle JIT Compiler : https://docs.oracle.com/en/database/oracle/oracle-database/18/jjdev/Oracle-JVM-JIT.html