본문 바로가기

Language/Java

[Java] Java의 컴파일 과정

자바의 컴파일 과정

  1. 개발자가 자바 클래스 파일(.java) 작성
  2. 소스 파일을 자바 바이트 코드(.class 파일)로 컴파일
    • JVM(자바 가상 머신) = 이해 가능
    • 컴퓨터 = 아직 이해 불가
  3. 바이트 코드를 클래스 로더(Class Loader)에게 전달
    • 클래스 로더는 동적 로딩(Dynamic Loading)을 담당하여 프로그램 실행 중 필요한 클래스 파일들을 로딩 및 링크하여 JVM 메모리(런타임 데이터 영역, Runtime Data Area)에 적재
    • 클래스 로더(Class Loader) 세부 동작
      • 로드 : 바이트 코드(클래스 파일)를 가져와서 JVM의 메모리에 로드
      • 검증 : 클래스가 자바 언어 명세(Java Language Specification) 및 JVM 명세에 맞는 지 검사하여 안전한 코드만이 실행되도록 함
      • 준비 : 클래스가 필요로 하는 메모리를 할당 (필드, 메서드, 인터페이스 등)
      • 분석 : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
        • 심볼릭 레퍼런스 : 클래스 파일 내 상수 풀(Constant Pool)에서 사용되는 참조 방식으로, 런타임 시점에 연결할 대상의 이름이나 위치만을 나타냄
        • 다이렉트 레퍼런스 : 심볼릭 레퍼런스를 메모리에서 직접 접근할 수 있는 참조로 변환한 것
        • 즉, MyClass라는 클래스 안에 myMethod() 라는 메서드가 있을 경우 클래스 파일 내 상수 풀에서는 MyClass.myMethod()로 접근할 수 있지만, JVM은 메모리 주소로만 접근 가능
        • Ex) MyClass.myMethod() → 0xAAAFFF031와 같이 변경하는 과정
      • 초기화 : 클래스 변수를 초기화하고 static 필드를 적절한 값으로 설정
  4. 실행 엔진(Excution Engine)JVM 메모리에 로드된 바이트 코드(클래스 파일)명령어 단위로 하나씩 실행
    • 실행 엔진의 구성
      • 인터프리터(Interpreter)
        • 바이트 코드 명령어를 한 줄씩 읽고 실행
        • 빠르게 시작할 수 있지만 전체적인 실행 속도는 느림
      • JIT 컴파일러(Just-In-Time Compiler)
        • 인터프리터의 단점을 보완하기 위해 도입
        • 바이트 코드 전체를 컴파일하여 바이너리 코드(기계어)로 변경한 뒤 이후에는 바이너리 코드를 실행
        • 이 과정에서 더 이상 인터프리팅할 필요가 없어 실행 속도가 빨라짐

인터프리터와 JIT 컴파일러 실행 시점
  • 최초 실행
    • 인터프리터만 사용
    • JIT 컴파일러는 사용되지 않음
  • 프로그램 실행 중
    • 자주 호출되는 코드가 발견되면 JIT 컴파일러는 해당 코드를 네이티브 코드(기계어)로 변환
    • 네이티브 코드(기계어)바이너리 형식(0과 1로 이루어진 이진코드)으로 저장되며, 이후 동일한 코드를 반복적으로 실행할 때이미 컴파일 된 네이티브 코드를 사용하여 훨씬 빠른 성능을 제공
      • 네이티브 코드는 디스크가 아닌 JVM의 메모리 캐시(Code Cache)에 저장
  • 즉, 최초 실행 시에는 인터프리터만을 사용하여 프로그램을 즉시 실행하고, 이후 프로그램 실행 중 JIT 컴파일러로 자주 호출되는 부분을 변환하여 실행 성능을 향상

모든 바이트 코드를 JIT 컴파일러로 저장하면 빠르지 않을까?
  • JIT 컴파일러가 훨씬 빠르다면 '인터프리터를 사용하는 대신 JIT 컴파일러만 사용하면 되지 않을까?' 라는 의문이 생김
  • JIT 컴파일러로 변환된 네이티브 코드는 JVM의 메모리 캐시(Code Cache)에 저장
  • Cache라는 것은 매우 빠른 속도를 갖지만 한정된 메모리이기 때문에 모든 바이트 코드를 네이티브 코드로 변환하여 메모리 캐시에 저장한다면 메모리 부족 문제가 발생할 수 있음
  • 따라서, JIT 컴파일러 '자주 사용되는 바이트코드'만을 메모리 캐시에 저장하는 것