Languages/Java

[Java] JVM (Java Virtual Machine)

dbssk 2023. 9. 6. 21:31

JVM(Java Virtual Machine)

  • 자바 프로그램 실행환경을 만들어 주는 소프트웨어이다. 모든 자바 애플리케이션은 JVM 위에서 실행되며, 이러한 특성은 자바가 "Write Once, Run Anywhere(WORA)"라고 불리는 특징을 구현하는 데 기여한다.
  • 자바 언어와 JVM의 관계 : 자바 언어로 작성된 프로그램은 컴파일러를 통해 바이트 코드(bytecode)라는 중간 형식으로 변환된다. 이 바이트 코드는 특정 운영 체제나 아키텍처에 의존하지 않으며, 이로써 자바 프로그램은 어떤 환경에서도 실행될 수 있다.
  • 운영 체제 독립성 : JVM은 특정 OS에 맞게 자바 바이트 코드를 해석하고 실행한다. 따라서 자바 프로그램은 운영 체제에 독립적이며, 자바 개발자는 프로그램을 한 번 작성하면 다양한 운영체제에서 실행할 수 있다. (단, JVM은 운영체제에 종속적이다.)
  • 언어 다양성 : JVM은 자바 뿐만 아니라 코틀린, 스칼라 등의 다른 언어로 작성된 프로그램도 실행할 수 있다. 
  • 성능 : 이전에는 JVM이 애플리케이션의 실행 속도를 느리게 만들 수 있다는 주장도 있었지만, 현대의 JVM은 JIT(Just-In-Time) 컴파일러와 최적화 기술을 사용하여 성능 문제를 크게 완화시켰다.

 

JVM 구성요소

클래스 로더가 컴파일도니 자바 바이트코드를 런타임 데이터 영역에 로드하고, 실행 엔진이 자바 바이트코드를 실행한다.

1. 클래스 로더(Class Loader)

자바의 특징 중 하나인 "동적 로드(Dynamic Loading)"를 담당한다. 동적 로드란, 자바 클래스가 컴파일 타임이 아니라 런타임에서 필요한 순간에 로드되고 링크되는 것을 의미한다.

클래스 로더들은 부모-자식 관계를 혈성하여 계층 구조를 형성한다. 이 계층 구조는 다음과 같이 구성된다.

  1. 부트스트랩 클래스 로더(Bootstrap Class Loader) 
    JVM을 시작할 때 가장 먼저 생성되며, 자바의 핵심 API와 Object 클래스 같은 핵심 클래스들을 로드한다. 네이티브 코드로 구현되어 있다.

  2. 익스텐션 클래스 로더(Extension Class Loader)
    기본 자바 API를 제외한 확장 클래스들을 로드한다. 보안 확장 및 기타 확장 기능을 여기에서 로드한다.

  3. 시스템 클래스 로더(System Class Loader)
    애플리케이션 클래스들을 로드하는 역할을 한다. 사용자가 지정한 클래스 패스($CLASSPATH)내의 클래스들을 로드한다.

  4. 사용자 정의 클래스 로더(User-Defined Class Loader)
    사용자가 직접 생성하여 사용하는 클래스 로더로, 애플리케이션의 클래스를 독립적으로 로드하도록 도와준다.

 

클래스 로드 과정

  1. 로드(Loading) : 클래스를 파일에서 가져와서 JVM 메모리에 로드
  2. 검증(Verifying) : 로드한 클래스가 자바 언어와 JVM 명세에 맞게 잘 구성되어 있는지 검사. 클래스 로드 과정에서 가장 복잡하고 시간이 많이 걸린다.
  3. 준비(Preparing) : 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메서드, 인터페이스 등을 나타내는 데이터 구조 준
  4. 분석(Resolving) : 클래스 내의 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경. 이 과정을 통해 클래스나 멤버의 심볼릭 레퍼런스가 실제로 어떤 클래스나 멤버를 가리키는지 연결된다.
  5. 초기화(Initializing) : 클래스 변수들을 초기화하고, static initializer를 실행하여 static 필드들을 설정된 값으로 초기화하고 클래스를 사용 가능한 상태로 만든다.
심볼릭 레퍼런스(Symbolic Reference)
- 클래스나 필드, 메서드와 같은 멤버에 대한 참조를 나타낸다.
- 구체적인 메모리 주소나 위치를 가리키지 않고, 멤버의 이름과 타입 등의 정보만을 포함하고 있다.
다이렉트 레퍼런스(Direct Reference)
- 실제로 클래스나 멤버에 대한 참조를 가리킨다.

 

2. 런타임 데이터 영역(Runtime Data Area)

런타임 데이터 영역은 JVM의 실행 중에 할당되고 사용되는 메모리 영역을 나타낸다. 6개의 영역으로 나눌 수 있는데 이중 PC 레지스터, JVM 스택, 네이티브 메서드 스택은 스레드마다 하나씩 생성되며, 힙, 메서드 영역, 런타임 상수 풀은 모든 스레드가 공유해서 사용한다.

  • PC 레지스터(Program Counter Register)
    • 스레드가 시작될 때 생성된다.
    • 현재 수행 중인 JVM 명령어의 주소를 가지고 있으며, 스레드가 명령어를 실행할 다음 위치를 가리킨다.

  • JVM 스택(JVM Stack)
    • 스레드가 시작될 때 생성된다.
    • 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택으로, 메서드 호출과 관련된 정보를 저장한다.
    • 지역 변수 배열(Local Variable Array)과 피연산자 스택(Operand Stack), 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀 포함하여, 메서드 호출 및 반환에 관한 데이터를 관리한다.

  • 네이티브 메서드 스택(Native Method Stack)
    • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다.
    • JNI(Java Native Interface)를 통해 호출되는 C/C++ 등의 코드를 실행하기 위한 공간이다.

  • 힙(Heap)
    • 인스턴스가 객체를 저장하는 공간이다.
    • 자바 객체가 생성되고 메모리를 할당하는 곳으로, 자바 프로그램의 데이터를 저장한다.
    • JVM 성능 및 가비지 컬렉션과 관련된 영역이다.

  • 메서드 영역(Method Area)
    • JVM이 시작될 때 생성된다.
    • 클래스와 인터페이스 정보, 펀타임 상수 풀, 필드와 메서드 정보, static 변수, 메서드의 바이트코드 등을 저장한다.
    • JVM 벤더마다 다양한 형태로 구현될 수 있다.

  • 런타임 상수 풀(Runtime Constant Pool)
    • 클래스 파일 포맷의 constant_pool 테이블에 해당하는 영역으로 메서드 영역에 포함된다.
    • 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 레퍼런스까지 담고 있는 테이블이다.
    • 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 사용한다.

 

3. 실행 엔진(Exceution Engine)

실행 엔진은 JVM 내에서 자바 바이트코드를 실제로 실행하는 역할을 한다. 바이트코드는 기계가 이해할 수 없는 코드이기 때문에 이것을 기계가 실행할 수 있는 코드로 변환하는데 이때, 실행 엔진은 두 가지 방식으로 동작한다.

1. 인터프리터(Interpreter)

  • 자바 바이트코드를 명령어 단위로 읽고 해석하여 실행한다.
  • 각 바이트코드 명령어는 1바이트 크기의 OpCode와 필요한 피연산자로 이루어져 있다.
  • 인터프리터는 바이트코드를 하나씩 해석하고 실행하기 때문에 실행 속도가 느릴 수 있으나, JVM을 구동하는 데 별도의 컴파일 단계가 필요 없어서 플랫폼 독립성을 제공한다.

 

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

  • 인터프리터의 성능 단점을 극복하기 위해 도입된 기술이다.
  • 인터프리터 방식으로 실행하다가 일정한 조건이 충족되면 해당 바이트코드를 네이티브 기계 코드로 컴파일한다.
  • 네이티브 코드는 컴퓨터의 CPU에서 직접 실행할 수 있으며, 인터프리터보다 빠르게 동작한다.
  • JIT 컴파일러는 바이트코드를 런타임에 컴파일하므로, 실행 중에 최적화 기회가 제공되어 성능을 향상시킬 수 있다.
  • 컴파일된 코드는 캐시에 저장되어 이후에 동일한 코드가 다시 실행될 때 재사용된다.

 

일반적으로 한 번만 실행되는 코드는 인터프리터로 실행하고, 자주 실행되는 코드 블록(핫스팟 코드)은 JIT 컴파일러를 통해 네이티브 코드로 변환한다. 

 

JVM 실행 과정

  1. 프로그램이 실행되면, JVM은 OS로부터 해당 프로그램이 필요로 하는 메모리를 할당받는다.
  2. 자바 소스 코드를 컴파일러(JAVAC)를 통해 자바 바이트코드(.class파일)로 변환한다.
  3. 변환된 클래스 파일들은 클래스 로더를 통해 JVM의 메모리 영역으로 로딩된다. 이때, 클래스의 정보와 코드 등이 메모리에 저장된다.
  4. 실행 엔진이 로딩된 클래스 파일을 해석하고, 바이트코드를 메모리 영역에 배치하여 프로그램을 실행한다.
  5. 프로그램 실행 중에 메모리 관리, 스레드 관리를 수행한다.

 

[참고] https://d2.naver.com/helloworld/1230

https://gyoogle.dev/blog/computer-language/Java/Java%20Virtual%20Machine.html