자바 코드 실행 과정

위 그림은 자바 코드의 실행 과정을 간략하게 보여 준다.
- 프로그램이 실행되면 JVM은 OS로부터 이 프로그램이 필요로 하는 메모리를 할당받는다. (JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.)
- 자바 파일(.java)이 자바 컴파일러에 의해 자바 바이트 코드(.class)로 변환된다.
- 클래스 로더를 통해 자바 바이트 코드를 JVM으로 필요한 시점에 로딩한다.
- 해석된 바이트 코드는 런타임 데이터 영역에 배치되어 실질적인 수행이 이루어지게 된다.
- 실행 과정 속에서 JVM은 필요에 따라 GC와 같은 관리 작업을 수행한다.
JVM의 구조

클래스 로더
자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스를 로드하고 링크하는 특징이 있다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더이다. 클래스 로더에는 로딩, 링크, 초기화 단계로 나뉘어져 있는데, 간단히 설명하면 다음과 같다.
로드
- 각 자바 바이트 코드(.class)는 JVM에 의해 메소드 영역에 다음 정보를 저장한다.
- 로드된 클래스를 비롯한 그의 부모 클래스의 정보
- 클래스 파일과 Class, Interface, Enum의 관련 여부
- 변수나 메소드 등의 정보
- 링크
- 검증: 읽어 들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사한다.
- 준비: 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스를 나타내는 데이터 구조를 준비한다.
- 분석: 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다.
- 초기화
- 클래스 변수를 적절한 값으로 초기화한다. 즉, static 필드들이 설정된 값으로 초기화된다.
런타임 데이터 영역
런타임 데이터 영역은 JVM이 운영 체제 위에서 실행될 때, 할당 받는 메모리 영역이며 총 6개의 영역으로 나눌 수 있다. 이 영역들은 스레드가 공유하는 공간인지 아닌지로 나눈다.
- 스레드마다 하나씩 생성되는 공간
- PC 레지스터
- 모든 스레드가 공유하는 공간
- 힙
PC 레지스터
- PC 레지스터는 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 생성된다.
- PC 레지스터는 메소드 안에서 바이트 코드 몇 번째 줄을 실행하고 있는지와 같은 정보를 갖고 있다.
JVM 스택
- JVM 스택은 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 생성된다.
- JVM 스택은 스택 프레임이라는 구조체로 이루어져 있는데, 새로운 메소드가 호출될 때마다 push, 메소드 실행이 끝나면 pop 동작을 수행한다.
- 각 스택 프레임은 지역 변수 배열, 피연산자, 프레임 데이터를 갖는다.
- 프레임 데이터는 현재 실행 중인 메소드가 속한 클래스의 런타임 상수 풀, 이전 스택 프레임에 대한 정보, 현재 메소드가 속한 클래스, 객체에 대한 참조 등을 말한다.

네이티브 메소드 스택
- 네이티브 메소드 스택은 자바 바이트 코드가 아닌 다른 언어로 작성된 네이티브 코드를 위한 스택이다.
- 성능 향상을 목적으로 작성되었다.
- JVM 스택과 네이티브 스택이 나뉘어져 있다 하더라도 자바 코드를 수행하다 JNI를 호출하면, JVM 스택에서 네이티브 메소드 스택으로 확장할 뿐이다.

힙
- 힙은 모든 스레드가 공유하는 영역이다.
- 힙은 프로그램을 실행하면서 생성된 모든 인스턴스 또는 객체를 저장하는 공간이다.

메소드 영역
- 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다.
- 클래스 로더가 클래스 파일을 읽어 오면, 클래스 정보를 파싱하여 런타임 상수 풀, 필드와 메소드 정보, static 변수, 메소드의 바이트 코드 등을 보관한다.
- 메소드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM에서는 흔히 PermGen(자바 1.7 이전), MetaSpace(자바 1.8 이후)로 부른다.
- 메소드 영역에 대한 GC도 JVM 벤더의 선택 사항이다.
런타임 상수 풀
- 런타임 상수 풀은 메소드 영역에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다.
- 각 클래스와 인터페이스의 상수 뿐만 아니라, 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다.
- 즉, 어떤 메소드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아서 참조한다.
실행 엔진
- 실행 엔진은 메모리에 적재된 바이트 코드(.class)를 기계어로 변경하여 명령어(instruction) 단위로 실행 및 바이트 코드를 운영체제에 맞게 해석해주는 역할을 수행한다. 실행 엔진이 바이트 코드를 명령어 단위로 읽어서 수행하는데 크게 두 가지 방식이 사용된다.
- 인터프리터
- 런타임 중에 바이트 코드를 한 줄씩 읽고 실행한다.
- 컴파일보다 속도가 느리다.
- JIT (Just In Time)
- 인터프리터의 속도 이슈를 해결하기 위해 같이 사용한다.
- 자주 실행되는 바이트 코드 영역을 런타임 중에 기계어로 컴파일하여 사용한다.