0. 들어가며
최근 자바 공부를 하면서 JIT 컴파일러와 일반 컴파일러의 차이가 문득 궁금해졌습니다.
처음에는 단순히 실행 속도를 조금 더 빠르게 해주는정도로만 생각했지만 JVM 내부 구조와 JIT의 동작 원리를 이해하면 일반 컴파일러와의 차이가 훨씬 명확해진다는 것을 알게 되었습니다.
이번 글에서는 일반 컴파일러와 JIT 컴파일러의 차이, JIT의 동작 과정, 장점과 느낀 점까지 정리해보겠습니다.
1. 일반 컴파일러: 정적 최적화의 구조
C나 C++ 같은 전통적인 언어의 컴파일러는 소스 코드를 실행 전에 한 번에 기계어로 변환합니다.
즉 프로그램이 실행되기 전에 이미 CPU가 이해할 수 있는 형태로 변환되기 때문에 초기 실행 속도는 빠릅니다.
하지만 단점도 있습니다. 실행 환경에서 실제 코드가 어떻게 쓰이는지는 알 수 없기 때문에 조건문 분기나 반복문의 실제 실행 패턴을 반영한 최적화는 어렵습니다.
일반 컴파일러는 코드 구조와 흐름을 분석해 정적 최적화를 수행합니다. 예를 들어 인라인 처리, 루프 언롤링, 불필요한 코드 제거 같은 최적화는 가능하지만 실행 중 데이터 패턴을 기반으로 한 최적화는 불가능합니다.
2. JIT 컴파일러: 런타임 최적화
자바는 먼저 소스 코드를 바이트코드(.class 파일)로 변환한 후 JVM 위에서 실행됩니다.
여기서 JIT 컴파일러가 등장합니다. JIT는 실행 중인 바이트코드를 분석하여 필요한 부분만 기계어로 변환하고 런타임 정보를 기반으로 최적화를 수행합니다.
JIT는 초기에는 인터프리터처럼 바이트코드를 바로 실행하며 동시에 각 코드의 실행 경로와 호출 빈도를 수집합니다. 반복적으로 호출되는 메서드나 루프는 JIT가 자동으로 “핫스팟”으로 식별하고 그 부분을 기계어로 변환하여 성능을 높입니다. 필요하다면 최적화 과정을 반복하며 점점 더 효율적인 실행 코드를 만들어갑니다.
이 과정 덕분에 JIT는 단순히 정적 분석만으로는 얻을 수 없는 동적 최적화를 수행할 수 있습니다.
3. 구조적·철학적 차이
일반 컴파일러는 실행 전에 가능한 모든 정보를 분석해 코드를 준비하는 정적 최적화에 초점을 둡니다. 반면 JIT는 실행 환경을 관찰하며 필요한 부분만 최적화하는 동적 최적화를 지향합니다.
이 철학의 차이는 기계어 코드 생성 방식에서도 나타납니다. 일반 컴파일러는 전체 프로그램을 기계어로 변환해 실행 파일을 만들지만 JIT는 처음에는 인터프리터로 실행하다가 실제로 자주 쓰이는 코드만 기계어로 변환하여 CPU 캐시에 적재합니다. 필요 없는 코드는 변환하지 않으며 오래된 코드나 잘 사용되지 않는 코드는 GC로 정리하기도 합니다. 이런 방식 덕분에 JIT는 메모리 효율까지 고려할 수 있습니다.
또한, 실행 모델 자체가 다릅니다. 일반 컴파일러는 완성된 실행 파일을 OS가 그대로 실행하는 구조(Ahead-of-Time)인 반면, JIT는 인터프리터가 먼저 실행하고 런타임 데이터를 수집하며, 그 정보를 바탕으로 기계어를 생성하고 최적화를 반복하는 구조입니다.
4. 마치며
처음에는 JIT를 단순히 실행 속도를 높이는 도구 정도로 생각했지만 공부하면서 그 역할이 훨씬 더 정교하다는 것을 알게 되었습니다.
특히 반복해서 실행되는 코드를 스스로 찾아서 최적화한다는 점이 인상깊었습니다. 사실 성능 문제나 JVM 최적화라는 건 먼 이야기 같았는데 JIT를 이해하고 나니까 “아, 프로그램이 실행될 때 JVM이 이렇게 코드를 관리하고 있구나”라는 감이 조금 잡히더라고요.
'자바' 카테고리의 다른 글
| [JVM 완전정복 #3] Runtime Data Area 완전분석: 메모리 구조와 각 영역 역할 (0) | 2025.11.17 |
|---|---|
| [JVM 완전정복 #2] 자바 프로그램의 첫 관문 - 클래스로더 (0) | 2025.11.17 |
| Java Class 하위에 변수를 몇개까지 선언할 수 있을까? (0) | 2025.11.11 |
| [JVM 완전정복 #1] JVM 구성요소 총정리: 가상 머신이 자바 코드를 실행하는 비밀 (0) | 2025.11.11 |
| 프로그래밍 언어의 시작 – 고급어와 저급어의 이해 (0) | 2025.11.04 |