Java - JVM

자바 소스코드를 class 파일로 컴파일해주고 실행하는 **JVM(Java Virtual Machine)**의 메모리 구조에 대한 이야기이다.

왜 JVM이 필요한가?

자바로 프로그래밍된 파일을 실행하려면 JVM이 필수적이다. 자바 소스코드는 그 자체로 OS에서 즉시 실행할 수 없고 자바 런타임환경에서 실행될 수 있는데, 그러려면 JVM이 운영체제에 맞게 바이트 코드로부터 기계어로 컴파일 해주어야 한다.

자바 애플리케이션 실행과정

운영체제에서 자바 프로그램을 실행하기 위한 과정은 다음과 같다.

  1. 프로그래머가 작성한 소스코드(.java)를 JDK에 내장된 자바 컴파일러(javac)로 컴파일한다. 컴파일 결과로 바이트 코드(.class)가 생성된다. 컴파일 하는 목적은 JVM을 위함이다. JVM은 바이트 코드만 읽을 수 있기 때문이다.
    (웹 개발시 Maven이나 Gradle로 빌드하면서 생성되는 jar파일은 이런 class 파일들의 집합이다.)
  2. 자바 컴파일러(javac)로 컴파일한 class 파일은 JVM 에서 구동할 수 있다. 그럼 JVM은 바이트 코드로부터 실행환경(운영체제)에 맞는 기계어(binary)로 컴파일 한다.
  3. 이렇게 컴파일된 기계어는 자바 런타임 환경(JRE)에서 애플리케이션(프로세스)으로 실행된다.

OS에서 바로 실행하는 것이 아닌 JVM이라는 가상머신을 통해 구동하기 때문에 Java는 플랫폼 독립적이라는 특징을 가질 수 있는 것이다.

JVM은 바이트 코드를 읽어서 프로세스로 동작시키는 것 외에도 가비지 컬렉터(GC)를 내장하고 있어서 GC가 메모리 관리를 해준다. 메모리 관리란, 이미 쓰임을 다한 메모리 주소를 반납하는 것을 의미하는데, C 계열의 언어에서는 프로그래머가 수동으로 해야하는 일이지만, 자바는 그럴 필요가 없다.

JVM과 함께 언급되는 키워드로 JDK, JRE가 있는데, JDK와 JRE는 서로 다른 환경에서 필요하다. JDK는 자바 애플리케이션을 개발하는 환경에서 필요하며, JRE는 자바 애플리케이션을 실행하는 환경에서 필요한 자원이다.

Java와 C의 차이

자바는 소스 코드(.java)로부터 바이트 코드(.class)로 컴파일 하고, 다시 바이트 코드로부터 기계어로 컴파일하는 과정을 거치지만, C에서는 바이트 코드 변환과정없이 소스코드에서 곧바로 기계어로 번역된다. 그래서 C가 자바보다 실행 속도가 더 빠르다고 이야기되곤 한다. 그렇다면 자바는 왜 C처럼 소스 코드를 기계어로 바로 컴파일하지 않고, 바이트 코드로 컴파일하는 과정을 거치는 걸까?

이유는 이미 위에 작성해두었다! ㅎㅎ


JVM 구동 원리

위에서 자바 애플리케이션의 구동원리를 크게 두번의 과정으로 설명했는데, 위의 과정에서 2번을 좀 더 상세히 정리해보려고 한다. 즉, JVM에서 어떻게 프로세스로 변환되는지에 대한 과정이다.

  1. 자바 애플리케이션이 실행되면, JVM은 이 애플리케이션의 실행에 필요한 메모리를 운영체제로부터 할당받는다. 이렇게 할당된 메모리는 용도에 따라 여러 영역으로 나누어 관리된다.
  2. 그리고 JVM의 클래스 로더에 의해 class 파일(바이트 코드)들이 JVM으로 로딩된다.
  3. 로딩된 class 파일은 Execution Engine을 통해 해석된다.
  4. 해석된 바이트 코드들이 Runtime Data Areas로 배치되어 프로세스로써 실질적인 수행이 이루어진다.

JVM의 구성

  1. ClassLoader

    • Runtime 시에 동적으로 클래스 파일들을 JVM으로 로딩하는데 사용하는 서브 시스템이다.
    • 클래스 로더에서 발생하는 행위는 아래 3가지로 구분할 수 있다.
      • Loading - JVM으로 로딩한다.
      • Linking - 필요한 영역으로 배치하는 역할을 수행.
      • Initialization
  2. Method Area

    • 클래스 정보를 메모리 공간에 처음 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다.
    • 올라가게 되는 메서드의 바이트 코드는 프로그램의 흐름을 구성하는 바이트 코드이다. 거의 모든 바이트 코드가 Method Area에 할당된다고 생각하면 된다.
    • 클래스 데이터를 위한 공간이라고 이해하면 좋다.
    • Method Area는 Constant runtime pool이라는 관리 영역도 존재하는데, 이곳에서 상수형 자료형을 저장하고 참조하여 중복을 막는 역할을 수행한다.
  3. Heap

    • 인스턴스 변수들과 배열 등 모든 변수가 Heap 에 저장된다.
    • new 키워드로 생성된 인스턴스 변수들이 할당되는 곳이라고 이해하면 된다.
    • Method Area가 클래스 데이터를 위한 공간이라면, Heap은 객체를 위한 공간이다.
  4. JVM Language Stacks

    • 메서드 안에서 사용되는 변수(local variable)들을 저장하는 영역이다.
    • 자바 애플리케이션 실행 과정에서 임시로 할당되었다가 메서드를 빠져나가며 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다.
    • 메서드 호출시마다 각각의 스택 프레임(해당 메서드만을 위한 공간)이 생성된다.
    • 메서드 수행이 끝나면 스택 프레임 별로 삭제를 한다.
  5. PC Register

    • 현재 실행중인 JVM의 주소를 저장한다.
    • 쓰레드가 시작될 때 생성되는 공간으로 쓰레드마다 PC 레지스터를 각각 갖는다.
    • 쓰레드가 어떤 명령에 의해 어떤 부분을 실행해야하는지를 기록하는 영역이다.
  6. Native Method Stacks

    • 자바 애플리케이션이 컴파일되어 생성되는 바이트 코드가 아닌 실제 실행할 수 있는 기계어(바이너리)로 작성된 프로그램을 실행시키는 영역이다.
  7. Execution Engine

    • 클래스를 실행시키는 역할을 수행한다.
    • 클래스 로더가 JVM 내의 Runtime Data Area에 바이트 코드를 배치시키면, 이것이 실행 엔진에 의해 실행된다.
    • 실행 엔진은 바이트 코드를 JVM에서 실제로 기계가 실행할 수 있는 형태인 바이너리로 변경한다.
  8. Native Method Interface

    • JVM 에서 실행중인 자바 코드가 라이브러리와 네이티브 애플리케이션들에 의해 호출되도록 허용하는 역할을 수행한다.
  9. Native Method Libraries

    • 실행 엔진에 필요한 네이티브 라이브러리들의 모음(컬렉션)이다.
  10. Run Time Area

    • 자바 애플리케이션을 실행하기 위해 운영체제에서 할당받은 메모리 영역이다.

함께 읽으면 훠얼씬 좋은 글