[Java] JVM

JVM, JRE, JDK

jdk-jre-jvm

각각의 의미는 아래와 같다. 오라클은 자바 11부터 JDK만 제공하고 있다.(JRE를 따로 제공하지 않음)

  • JVM(Java Virtual Machine): 컴파일된 바이트코드(class 파일)를 OS에 특화된 코드로 변환하는 표준이자 구현체
  • JRE(Java Runtime Environment): 자바 애플리케이션을 실행할 수 있도록 구성된 배포판
  • JDK(Java Development Kit): JRE + 개발에 필요한 툴

JVM 구조

JVM 구조

JVM은 처음에 클래스 로더를 통해 클래스 및 인터페이스를 로딩, 링크, 초기화하고 public static void main(String[])을 실행한다.

클래스 로더 (Class Loader)

클래스 로더는 컴파일된 class 파일에서 바이트코드를 읽어 메모리에 로드한다.

  • 로딩(Loading): class 파일을 로딩하여 메타정보를 담은 Class 객체를 생성한다.
    • 아래와 같은 것들을 런타임 상수 풀에 저장한다.
      • 컴파일 시 알 수 있는 숫자 리터럴(Literal)
      • 런타임에 결정(Resolve)되어야 하는 필드 및 메서드 레퍼런스(Reference) 등
    • 클래스의 Class 객체를 생성하여 힙에 저장한다.
  • 링크(Linking): 레퍼런스를 연결한다.
    • 검증(Verify): class 파일이 구조적으로 올바른지 검증한다.
    • 준비(Prepare): 클래스의 정적 필드(Static Field)를 생성하고 기본값으로 초기화한다.
      • 기본값으로 초기화한다는 것은 코드를 실행하는 것이 아니라 정적 필드가 들어갈 메모리를 준비해 둔다는 느낌이다. 정적 필드의 값 할당은 초기화 과정에서 실행된다.
    • 결정(Resolve): 심볼릭 레퍼런스(Symbolic Reference)가 구체적인 값을 가리키도록 결정한다.
      • JVM의 명령어들은 런타임 상수 풀의 심볼릭 레퍼런스에 의존한다. 따라서 이를 구체적인 값을 가리키게하여 명령을 수행할 수 있도록 한다.
      • 이 단계에서 반드시 수행되는 것은 아니다.
  • 초기화(Initialization): 클래스의 초기화 메서드를 실행한다.
    • 정적 필드에 값을 할당하거나 스태틱 블록(Static Block)을 실행한다.

메모리 (Run-Time Data Areas)

  • 힙(Heap): 객체(실제 인스턴스)를 저장한다.
    • 사용이 끝난 힙 메모리는 가비지 컬렉터에 의해 정리된다.
    • 모든 쓰레드가 공유한다.
  • 메서드 영역(Method Area): 런타임 상수 풀, 메서드 및 생성자 코드 등을 저장한다.
    • 런타임 상수 풀(Run-Time Constant Pool): class 파일의 constant_pool에 있는 심볼릭 레퍼런스(Symbolic Reference)와 정적 상수(Static Constants)를 저장한다.
    • 모든 쓰레드가 공유한다.
  • PC 레지스터(Program Counter Register): 실행되어야 할 JVM 명령어 주소를 저장한다.
    • 쓰레드 내에서만 공유된다.
  • 스택(Stack): 프레임을 저장한다.
    • 프레임(Frame): 메서드가 호출되면 새로운 프레임이 생성되고, 호출이 끝나면 파괴된다. 메서드의 지역 변수와 부분 결과를 들고 있고, 메서드 호출 및 반환의 역할을 담당한다.
    • 쓰레드 내에서만 공유된다.
  • 네이티브 메서드 스택(Native Method Stack): 네이티브 메서드를 호출할 때 사용하는 스택이다.
    • 네이티브 메서드: 성능 등의 이유로 C, C++ 언어로 작성된 메서드다. JNI(Java Native Interface)를 통해 호출한다.
    • 쓰레드 내에서만 공유된다.

실행 엔진 (Execution Engine)

실행 엔진이 어떻게 동작하는지는 JVM 명세에 규정되지 않았다. 따라서 JVM 벤더들은 다양한 기법으로 실행 엔진을 향상시키고, 다양한 방식의 JIT 컴파일러를 도입하고 있다.

  • 인터프리터(Interpreter): 바이트코드를 명령어 단위로 하나씩 읽어서 해석하고 실행한다.
    • 바이트코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다.
  • JIT(Just In Time) 컴파일러
    • 인터프리터가 반복되는 코드를 발견하면, 바이트 코드를 네이티브 코드로 컴파일한다.
    • 반복되는 코드를 만났을 때, 인터프리팅하지 않고 컴파일해두었던 네이티브 코드를 사용한다.
    • 인터프리터 효율을 증가시킨다.
  • 가비지 컬렉터(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리한다.

요약

  1. 클래스 로더가 컴파일된 자바 바이트코드를 읽고 메모리에 로드한다.
  2. 실행 엔진이 바이트코드를 한 줄씩 실행한다.
  3. 한 줄씩 하는건 비효율적이니까 JIT 컴파일러도 돕는다.
  4. 메모리도 최적화해야하니까 중간중간 가비지 컬렉션한다.

댓글 남기기