JVM & JIT 컴파일러

hs·2025년 11월 4일

인터프리터 vs 컴파일러

  • 인터프리터: 코드를 한 줄씩 읽어서 즉시 번역하고 실행
    • 장점: 전체 코드를 미리 번역할 필요 없이 즉시 실행 → 시작 속도가 빠름
    • 단점:
      • 매번 번역 과정을 거침 → 같은 코드가 반복될 때 비효율적
      • 전체 실행 속도가 느림
  • 컴파일러: 코드를 전체적으로 미리 읽어 한 번에 네이티브 코드(기계어)로 변환
    • 장점: 번역 과정 없이 네이티브 코드를 바로 실행하므로 실행 속도가 매우 빠름
    • 단점: 전체 코드를 번역하는데 시간이 걸림 → 초기 시작이 느림
  • 자바는..?
    • 1차 컴파일 (javac): 소스 코드 (.java) -> 바이트코드 (.class)
      • 자바 소스 코드(.java)는 javac 컴파일러에 의해 컴파일됨
      • 컴파일 과정을 통해 바이트코드(.class) 생성
      • 바이트코드는 JVM이 이해할 수 있는 중간 언어
      • CPU가 직접 이해하고 실행 불가
    • JVM 런타임에서의 변환: 바이트코드 (.class) -> 네이티브 코드
      • java 명령어로 .class 파일을 실행하면 JVM이 동작
      • JVM은 바이트코드를 네이티브 코드로 변환하여 실행

HotSpot JVM

  • 인터프리터
    • JVM은 처음에 바이트코드를 한 줄씩 읽어서 네이티브 코드로 변환하고 실행
    • 컴파일 하지 않아 빠르게 시작됨
  • JIT 컴파일러
    • JVM은 코드를 실행하면서 자주 사용되는 코드를(Hot Spot) 프로파일링
    • 특정 코드가 핫 스팟으로 감지되면, 해당 바이트코드를 네이티브 코드로 컴파일
    • 네이티브 코드는 메모리에 캐싱, 이후 동일한 코드가 실행될 때 네이티브 코드가 즉시 실행

JIT(Just-In-Time) 컴파일러

C1 컴파일러 (Client Compiler)

  • 목표: 빠른 시작 속도와 응답성
  • 특징:
    • 컴파일 속도가 매우 빠름
    • 애플리케이션 시작 시 초기 성능 향상 목표
    • 기본적인 최적화만 수행
      • 메서드 인라이닝(inlining)
      • 간단한 루프 최적화
    • 주로 데스크톱 애플리케이션이나 실행 시간이 짧은 프로그램에 적합
  • 역할:
    • 계층적 컴파일의 초기 단계
    • 인터프리터에 의해 실행되던 코드가 호출 횟수의 임계점을 넘어가면 C1이 네이티브 코드로 컴파일하여 1차적인 성능 향상을 제공
    • C2 컴파일러가 사용할 프로파일링 데이터(profiling data)를 수집

C2 컴파일러 (Server Compiler)

  • 목표: 높은 성능 (Peak Performance)
  • 특징:
    • C1 컴파일 단계에서 수집된 프로파일링 데이터를 기반으로 코드 동작을 분석
    • C1보다 느리지만 정교한 최적화 수행
      • 공격적 메서드 인라이닝(Aggressive Inlining): 더 많은 메서드를 호출 지점에 직접 삽입하여 오버헤드를 제거
      • 루프 언롤링(Loop Unrolling): 루프를 풀어써서 분기 예측 실패를 줄이고 실행 속도를 높임
      • 탈출 분석(Escape Analysis): 객체가 메서드 외부로 탈출하지 않는 경우, 힙(Heap) 대신 스택(Stack)에 할당하거나 락(lock)을 제거하여 성능을 향상
    • 주로 오랜 시간 동안 중단 없이 실행되어야 하는 서버 애플리케이션에 최적화
  • 역할:
    • 계층적 컴파일의 최종 단계
    • C1에 의해 컴파일된 코드 중 핵심 코드를 C1이 수집한 프로파일링 데이터를 기반으로 더 높은 수준의 최적화를 수행

계층적 컴파일 (Tiered Compilation)

도입 전

  • -client 또는 -server 명령 옵션을 사용해 JIT 컴파일러를 직접 선택
  • client 옵션 (C1 컴파일러)
    • 목표: 빠른 시작 속도와 낮은 메모리 사용량
    • 적합: 데스크톱 애플리케이션이나 짧게 실행되는 프로그램
    • 제한: 복잡한 최적화를 수행하지 않아 장기적인 피크 성능 낮음
  • server 옵션 (C2 컴파일러)
    • 목표: 장시간 실행되는 애플리케이션의 최고 성능
    • 적합: 서버 애플리케이션
    • 제한: 컴파일 시간이 오래 걸려 애플리케이션의 시작이 느림

도입 후

  • Level 0 (인터프리터):
    • 역할: 초기 단계, 바이트코드를 한 줄씩 해석하며 실행
    • 정보 수집: 동시에 메서드 호출 횟수, 루프 반복 횟수 등 기본적인 프로파일링 정보 수집
    • 목표: 애플리케이션의 빠른 초기 시작을 담당
  • Level 1, 2, 3 (C1 컴파일러):
    • 진입 조건: 인터프리터에 의해 실행되던 코드가 일정 호출 임계값을 넘어서면 C1 컴파일러가 개입
    • 역할:
      • 바이트코드를 빠르게 네이티브 코드로 컴파일하여 즉각적인 성능 향상을 제공
      • C1은 기본적인 최적화를 수행하며, 동시에 더 상세하고 정확한 프로파일링 정보를 계속 수집
    • 레벨: 내부적으로 여러 최적화 레벨(1, 2, 3)이 있어, 프로파일링 정보 수집 정도나 최적화 강도 조절
  • Level 4 (C2 컴파일러):
    • 진입 조건: C1에 의해 컴파일된 코드 중에서 실행 빈도가 매우 높고, 성능에 큰 영향을 미치는 핵심 코드가 발견되면 C2 컴파일러가 최종적으로 개입
    • 역할:
      • C1이 수집한 프로파일링 데이터를 기반으로 해당 코드를 다시 컴파일
      • 고급 최적화를 수행하여 최상의 장기적 성능(피크 성능)을 달성하는 네이티브 코드 생성

GraalVM

Oracle에서 개발한 고성능 JDK(Java Development Kit) 배포판

  • 고성능 JIT(Just-In-Time) 컴파일러 (Graal Compiler):
    • GraalVM의 JIT 컴파일러는 자바로 작성 (기존 HotSpot JVM의 C1/C2 컴파일러는 C++)
    • 모듈화, 유지보수성, 확장성 향상
  • AOT(Ahead-Of-Time) 컴파일 (Native Image):
    • Java 바이트코드를 실행하기 전에(Ahead-of-Time) 특정 운영체제와 아키텍처에 맞는 네이티브 실행 파일로 미리 컴파일하는 기술
    • 네이티브 실행 파일은 JVM 없이 단독으로 실행 가능
    • JVM의 워밍업(JIT 컴파일 시간)이 필요 없어 시작 속도가 매우 빠르고 메모리 사용량이 낮다
    • 마이크로서비스나 서버리스 함수처럼 빠른 시작 시간과 낮은 메모리 사용량이 중요한 경우에 유리
    • 런타임 정보(프로파일링 데이터)를 활용할 수 없어 JIT 컴파일러가 동적으로 수행하는 최적화 불가능
      • 장시간 실행되는 애플리케이션의 최대 피크 성능은 JIT 컴파일 애플리케이션보다 약간 낮을 수 있다
  • 다중 언어 지원 (Polyglot Programming) 및 Truffle 프레임워크:
    • 자바뿐만 아니라 자바스크립트, 파이썬, 루비, R, WebAssembly, C/C++와 같은 언어들을 하나의 런타임 환경에서 실행하고 상호 운용할 수 있도록 지원
profile
sh

0개의 댓글