Runtime Data Area
런타임 데이터 영역(Runtime Data Area)은 실제 클래스 파일이 적재되는 곳으로 JVM이 OS로 부터 자바 프로그램 실행을 위한 데이터와 명령어를 저장하기 위해 할당받는 메모리 공간이다.
- 메소드 영역
- 가장 먼저 데이터가 저장됨
- 클래스 로더에 의해 로드된 클래스, 메소드 정보와 클래스 변수 정보 저장
- 클래스 변수 남발 시 메모리 공간 부족할 수 있음
- Java 7의 경우 부족할 수 있었으나 Java 8부터는 개선됨
- 프로그램 시작부터 종료될 때까지 메모리에 적재
- 명시적 null 선언 시 GC가 청소
- 모든 스레드가 공유함
- 힙 영역
- 런타임 시 결정되는 참조 자료형이 저장됨
- 런타임 시 결정됨에 따라 동작 중의 문제(범위 초과 참조 등)가 발생할 코드임에도 문법의 문제는 아니기에 컴파일 시 에러를 출력하지 않음
- new 연산자를 통해 생성된 객체(인스턴스)가 저장되는 공간
- 즉, 인스턴스 변수도 여기에 저장됨
- 객체가 더 이상 쓰이지 않거나 명시적 null 선언 시 GC가 청소
- 모든 스레드가 공유함
- 런타임 시 결정되는 참조 자료형이 저장됨
- 스택
- 컴파일 시 결정되는 기본 자료형(& 참조 변수)이 저장됨
- 컴파일 시 결정됨에 따라 자료형의 범위를 초과한 값 할당 등의 코드가 컴파일 단계에서 검출됨
- 참조 변수는 기본 자료형을 Wrapper Class로 boxing한 변수(Integer, Byte 등)
- 메소드 호출 시 메모리에 FILO로 삽입
- 메소드 종료 시 메모리에서 LIFO로 제거
- 메소드가 호출될 때마다 각각의 스택 프레임이 생성됨
- 각 스택 프레임은 하나의 메소드에 대한 정보를 저장
- {} 혹은 메소드가 종료될 때 삭제됨
- 메소드 종료 시 프레임 별로 삭제
- 각 스레드 별로 생성
- 컴파일 시 결정되는 기본 자료형(& 참조 변수)이 저장됨
- PC 레지스터
- JVM이 수행할 명령어의 주소를 저장하는공간
- OS의 PC 레지스터와 유사한 역할이나 CPU와는 별개로 JVM이 관리
- 스레드가 시작될 때마다 생성됨
- 각 스레드 별로 생성
- JVM이 수행할 명령어의 주소를 저장하는공간
- 네이티브 메소드 스택
- 바이트 코드가 아닌 기계어로 작성된 코드를 실행하는 공간
- 다른 언어(C/C++)로 작성된 코드를 수행하기 위함
- Java Native Interface를 통해 바이트 코드로 변환됨
- Java Code를 수행하다 JNI 호출 시 Java Stack에서 Native Stack으로 동적 연결(Dynamic Linking)을 통해 확장됨
- 따라서 나뉘어졌다고는 하나 stack에서 연결할 수 있음
- JNI(Java Native Interface) 호출 시 생성
- 각 스레드 별로 생성
Runtime Data Area의 구분
클래스 변수는 Method Area에 저장된다는데 오라클 문서에 따르면 logical하게는 heap에 속한다 설명한다.
뭔가 아예 별개로 설명하자니 애매하다. 엄밀하게 영역별로 어떻게 구성되는 지 따져보자.
Java 8 이전의 Heap 구조
위 그림이 일반적으로 JVM의 heap 구조를 설명할 때 사용하는 그림이다.
그런데 permanent 영역은 보통 heap 취급을 하지 않지만 이렇게 heap 영역에 포함된 상태다.
그런데 일반적으로 permanent 영역과 heap 영역은 구분해서 설명한다.
그리고 Method 영역은 permanent 영역에 속해 있어 heap 영역이 아니라고 보통이야기 한다.
따라서, heap 영역에 포함되어 있기는 하나 heap 영역과는 구분해서 간주함으로 인해 Method 영역을 오라클 문서에서는 logical하게 heap의 한 부분이라고 설명한다.
Heap의 Permanent Generation 영역 변화
Java 7 JVM
<----- Java Heap -----------------> <--- Native Memory --->
+------+----+----+-----+-----------+--------+--------------+
| Eden | S0 | S1 | Old | Permanent | C Heap | Thread Stack |
+------+----+----+-----+-----------+--------+--------------+
<--------->
Permanent Heap
S0: Survivor 0
S1: Survivor 1
Java 8 JVM
<----- Java Heap -----> <--------- Native Memory --------->
+------+----+----+-----+-----------+--------+--------------+
| Eden | S0 | S1 | Old | Metaspace | C Heap | Thread Stack |
+------+----+----+-----+-----------+--------+--------------+
Permanent 영역은 JVM에 의해 크기가 제한된 영역으로 Java 7까지 유지되었다. 따라서 영역 제한으로 인해 메모리 범위 초과 문제가 있었다.
대신 Java 8 부터는 Permanent Generation을 제거하고 Metaspace로 대체하였고 heap이 아니라 JVM에 의해 메모리가 제한되지 않는 Native Memory 영역으로 전환하여 OS에 의해 메모리 할당 공간이 자동으로 조절되므로 이론상 아키텍쳐가 지원하는 메모리 크기까지 확장할 수 있다.
따라서, 애매하게 heap에 걸쳐있던 permanent 영역이 non-heap이라고 구분하던 과거와는 달리 명확하게 method area는 heap이 아니라고 정의할 수 있게 되었다.
변경 이유는 ArrayList와 같은 레퍼런스 타입의 동적 배열 객체를 static으로 생성하면 레퍼런스를 Permanent 영역에 저장하는데 해당 객체 배열에 객체 원소를 추가하면 그대로 static object의 레퍼런스가 Permanent 여역에 쌓을 뿐만 아니라 string literal data를 저장하던 string pool도 permanent 영역에 저장하느라 OOM 에러가 발생하는 이슈가 잦았다고 한다.
Permanent에서 Metaspace로 변경됨에 따른 변화
Java8부터는 클래스 메타데이터는 native memory로 이동된 Metaspace에 저장하고 permanent에 저장했던 interned strings와 static 변수는 heap영역으로 보낸다.
Java8 부터는 static 변수를 heap영역에서 관리함은 GC 대상이 될 수 있음을 의미한다.
으레 다들 설명하듯 PermGen에 속한 Method area가 클래스 변수를 저장한다고 알고 있다면 이해하기 쉽지 않다.
static 변수는 클래스 변수로 명시적 null 선언이 되지 않으면 gc되어서는 안되는 변수다.
Method area가 클래스 변수를 저장한다고 이해하는 시점에서 오해가 발생한다.
Method area는 class의 메타데이터를 저장할 뿐 실질적인 객체와 데이터는 Method area 바깥의 PermGen에 저장된다.
클래스 메타데이터가 metaspace로 이동하고 기존에 permanent 영역에 저장되어 있던 static object는 heap영역에 저장되도록 변경되었다고 설명하는데 이는 reference는 여전히 metaspace에서 관리됨을 의미하기에 참조를 잃은 static object는 GC의 대상이 될 수 있으나 reference가 살아있다면 GC의 대상이 되지 않음을 의미한다.
따라서, metaspace는 여전히 static object에 대한 reference를 보관하며 애매하게 heap에 걸쳐지지 않고 non-heap(native memory)로 이관되며 static 변수(primitive type, interned string)는 heap 영역으로 옮겨짐에 따라 GC의 대상이 될 수 있게끔 조치한 것이다.
stack 영역
PC 레지스터와 스택, Native Method 스택은 각 스레드마다 생성된다. 그리고 스택에는 메소드 호출마다 프레임이 생성되어 쌓이며 프레임에는 리턴할 값, 지역 변수, 연산자 스택, 현재 클래스 constant pool 의 값을 호출할 레퍼런스가 있다. 이 레퍼런스를 통해 클래스, 인스턴스 변수들이나 생성된 참조 자료형을 호출한다.
참고 자료 :
'백엔드 > Java' 카테고리의 다른 글
[Java] 스트림(Stream) (0) | 2023.05.11 |
---|---|
[Java] 람다(Lambda) (0) | 2023.05.09 |
[Java] 동작 파라미터화(Behavior Parameterization) (0) | 2023.05.09 |
[Java] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.04.20 |
[Java] 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2023.04.20 |