JVM 내부구조
데이터 타입
원시 타입
byte
8비트의 정수 자료형, 기본값은 0이며 -128 ~ 127의 범위를 가진다.
short
16비트의 정수, 기본값은 0이며 -32768 ~ 32767의 범위를 가진다.
int
32비트의 정수, 기본값은 0이며 -2147483648 ~ 2147483647의 범위를 가진다.
long
64비트의 정수, 기본값은 0이며 9223372036854775808 ~ 9223372036854775807의 범위를 가진다.
char
16비트의 문자, 16비트의 부호 없는 정수로 나타내고 UTF-16으로 인코딩되며 기본값은 null(‘\u0000’)이다. 0 ~ 65535의 범위를 가진다
float
32비트의 부동 소수점, 기본값은 양수 0이다
double
64비트의 부동 소수점, 기본값은 양수 0이다.
returnAddress
JVM 명령어의 opcode에 대한 포인터이다. 일반적인 자바로 실행되는 프로그램을 개발하는 개발자가 직접 다룰일은 많지 않을 것 같다.
boolean
boolean 유형은 애매한게 정의는 되어 있지만 JVM 내부에 별도로 지원하는 명령어는 존재하지 않아 제한적으로 제공된다고 한다.
내부적으로는 int 자료형을 사용해서 컴파일된다고 한다.
참조 타입
참조 타입에는 클래스, 배열, 인터페이스가 존재한다고 한다.
런타임 데이터 영역
JVM 프로그램 실행 중에 사용되는 영역이다. 일부는 JVM 시작 시 생성되고 JVM이 종료될 때만 삭제되며, 그 외에는 스레드별로 관리된다. 스레드별 데이터 영역은 스레드가 생성될 때 생성되고 스레드가 종료되면 소멸된다.
PC 레지스터, JVM 스택, 네이티브 메소드 스택은 스레드별로 생성되며 힙, 메소드영역, 런타임 상수풀은 스레드끼리 공유할 수 있다.
스레드별 영역
pc 레지스터
JVM은 다중 스레드를 지원한다. JVM의 각 스레드에는 자체 pc(프로그램 카운터) 레지스터가 존재하며, JVM의 스레드는 해당 스레드에 대한 메서드를 실행하는데,
해당 메소드가 네이티브가 아닌 경우 pc 레지스터에는 현재 실행 중인 JVM의 명령어의 주소를 포함시킨다. 현재 스레드가 실행 중인 메소드가 네이티브이면 JVM의 pc 레지스터 값은 정의되지 않는다.
라는 문서의 내용을 추측해보면 아마 자바를 통해서 실행하는 메소드가 아닌, 다른 프로세스의 메서드를 호출할 경우를 말하는 듯 하다.
JVM 스택
각각의 JVM 스레드는 생성 시 JVM 스택이 같이 생성된다. JVM 스택은 프레임을 저장하며(차후 설명), JVM 스택은 C와 같은 기존 언어의 스택과 유사하다. JVM 스택은 푸시 및 팝 프레임을 제외하고는 직접 조작되지 않으므로 프레임이 힙에 할당될 수 있으며, JVM 스택의 메모리는 연속적일 필요가 없다.
JVM 스택은 고정된 크기일수도, 동적으로 확장 및 축소될 수도 있지만 다음과 같은 상황에는 예외를 던질 수 있다.
- 스레드에 할당된 용량보다 더 큰 JVM 스택 용량이 필요한 경우 JVM은 StackOverflowError를 발생시킨다
- JVM 스택이 동적으로 확장을 시도했지만 메모리가 부족하거나, 새로 스레드를 생성하며 JVM 스택 생성을 해야하는데 메모리가 부족한 경우 OutOfMemoryError 에러를 발생시킨다.
네이티브 메서드 스택
자바 외에 언어로 작성된 메소드 지원하기 위해 존재하는 공간이다. 역시나 크기는 가변적이다.
이런 상황에서는 예외가 발생한다.
- 스레드 계산에 허용된 것보다 더 큰 메소드 스택이 필요한 경우 StackOverflowError 던짐.
- 크기 조절 시 가용 메모리가 부족한 경우 or 스레드 생성 시 초기 네이티브 메서드 스택 생성에 필요한 메모리가 부족한 경우 OutOfMemoryError 발생.
공통 영역
힙 영역
JVM의 모든 스레드 간에 공유되는 공간이며, 모든 클래스 인스턴스 및 배열이 할당되는 런타임 데이터 영역이다.
힙은 가상 머신 시작 시 생성되고 객체들을 명시적으로 할당 or 해제 할 수 없으며 가비지 컬렉터에 의해 회수된다. 힙 역시 고정된 크기일수도, 동적으로 확장 및 축소될 수 있다. 힙에 대한 메모리 또한 연속적일 필요는 없다.
다음과 같은 상황에는 예외를 발생시킨다.
- JVM 연산에 현재 할당된 메모리 용량보다 더 많은 힙이 필요한 경우 OutOfMemoryError 에러를 던진다.
메소드 영역
메소드 영역 또한 모든 스레드 간에 공유되는 영역이다. 런타임 상수 풀, 필드 및 메서드 데이터 등 클래스의 메서드 및 생성자에 대한 코드를 저장한다.
JVM 시작 시 생성되며 힙의 일부이지만 소규모의 프로젝트에서는 GC의 대상에서 제외시키는 방법도 있다고 한다. 메소드 영역도 고정된 크기이거나 필요한 대로 확장 및 축소될 수 있다. 메소드 영역의 메모리도 연속적일 필요는 없습니다.
다음과 같은 상황에서는 예외를 발생시킨다.
- 데이터를 올리기에 메소드 영역의 메모리가 충분치 않을 때 OutOfMemoryError 발생.
런타임 상수 풀
런타임 상수 풀(constant pool)은 컴파일 타임에 감지된 숫자 리터럴, 런타임 동안 작동하는 메서드 및 필드에 대한 참조 등등 여러 종류의 상수가 포함된다.
JVM의 메서드 영역에 포함되며 클래스 또는 인터페이스가 생성될 때 구성된다.
다음과 같은 상황에서는 예외가 발생한다.
- 클래스나 인터페이스 생성 시 런타임 상수 풀 구성에 필요한 메모리가 JVM의 메서드 영역에서 사용할 수 있는 것보다 더 많은 메모리가 필요한 경우 OutOfMemoryError 발생.
프레임
프레임은 데이터 및 진행중인 연산의 결과를 저장하거나 메서드에 대한 값을 반환하며 예외를 전달하는 데 사용된다.
메소드가 호출될 때마다 새 프레임이 생성되며, 실행 결과가 정상적이든 망했든 관계없이 메서드 호출이 완료되면 프레임은 삭제된다. 프레임은 JVM 스택에 할당되며, 각 프레임에는 자체 지역 변수 배열, 자체 피연산자 스택 및 현재 실행중인 메서드가 속해있는 클래스의 런타임 상수 풀에 대한 참조가 있다고 한다.
실행중인 메서드가 다른 메서드를 호출하면 새 프레임이 생성되고 이것이 현재 프레임으로 설정된다. 메서드 반환 시 현재 프레임은 메서드 호출 결과를 이전 프레임으로 다시 전달하고, 쓸모가 없어진 프레임은 삭제된다.
프레임은 스레드별로 할당되기 때문에 다른 스레드에서 참조할 수 없다.
예외
JVM의 예외는 Throwable 클래스의 인스턴스 or 해당 하위 클래스 중 하나이며, 다음과 같은 세 가지 이유 중 하나에 포함되면 예외를 발생시킨다.
1. throw로 던지기
2. JVM이 비정상적인 실행 조건 감지 시 발생
- 잘못된 명령이 실행될 경우 (ex: 배열 범위를 벗어나는 인덱싱)
- 프로그램의 일부를 로딩하거나 링크하는데 오류가 발생한 경우
- 메모리 사용량이 너무 과다할경우
3. JVM 내부 오류 감지 시 예외 발생
참고
- Oracle, Chapter 2. The Structure of the Java Virtual Machine, https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html