JVM Runtime Data Area Structure 본문
개요
Java 8 부터 JVM 의 Runtime Data Area 에 변화가 생겼는데, Java 8 이전 버전까지 존재했던 Permanent Generation 메모리 영역이 사라지고 Metaspace 영역이 새로 생긴 것이다. 아래가 그 변화를 가장 잘 나타내주는 그림이라고 볼 수 있다.

위 그림을 보면 마치 Permanent Generation 의 역할을 Metaspace 가 대체하는 것처럼 보일 수 있다. 지금 글을 작성하고 있는 본인도 Metaspace 가 단순히 Permanent Generation 을 대체하는 것인지에 대한 의문이 들어 여러가지 자료들을 찾아보았고, 그 결과를 정리하기 위해 이 글을 작성한다. 모든 기술 블로그가 그러하듯, 본인의 블로그도 틀린 점이 존재할 수 있기에 무조건적인 신뢰는 삼가라는 말을 독자에게 권하고 싶다. 잘못된 정보에 대한 피드백은 언제나 환영한다.
Runtime Data Area
Runtime Data Area 는 JVM 이 OS 로부터 자바 프로그래밍 실행을 위한 데이터와 명령어를 저장하기 위해
할당받는 메모리 공간이다.
Runtime Data Area 는 5개의 주요 영역으로 구분할 수 있다.
- Static Area (=Method Area)
- Heap Area
- Stack Area
- PC Register
- Native Method Stack
이 파트에서는 위 다섯 개의 영역 중 Static Area 와 Heap Area 그리고 Stack Area 가 JVM 내에서 각각 어떤 역할을 수행하는지 알아보려고 한다. 위 다섯 개의 영역은 자바의 동작 방식과 매우 크게 연관되어 있기에 자바를 사용하는 프로그래머라면 한 번쯤은 보고 가는 것이 좋다고 생각한다.
Static Area (=Method Area)
Static Area 는 가장 먼저 데이터가 적재되는 영역으로 JRE 가 main() 메소드의 존재 유무를 확인한 후, JVM 은 java.lang 패키지를 Static Area 에 적재한다.
스태틱 멤버 변수 혹은 클래스 멤버 변수라고 불리는 변수, 클래스와 메소드의 메타 데이터가 저장되며, 개발자가 작성한 모든 클래스와 import 된 패키지도 이곳에 적재된다. Class Loader 에 의해서 해당 클래스가 사용되는 시점에 적재되며, Static Area 에 적재된 데이터들은 JVM 이 종료될 때까지 고정된 상태로 해당 영역에 상주한다.
Static Area 에 적재된 데이터들은 Multi Thread 환경에서 모든 Thread 에 의해 공유된다.
Heap Area
new 명령어로 생성된 인스턴스와 배열 그리고 참조 자료형이 적재된다. GC (Garbage Collector) 의 관리를 받으며, 명시적으로 null 을 선언할 경우 해당 객체는 GC 에 의해 수거된다.
Static Area 와 동일하게 적재된 데이터들은 Multi Thread 환경에서 모든 Thread 에 의해 공유된다.
Stack Area
메소드 내에서 사용되는 기본 자료형, 매개 변수, 지역 변수, 리턴값, 참조 변수가 적재된다.
메소드 호출 시, FILO (First In Last Out) 형태로 삽입되고, 메소드 종료 시에는 LIFO (Last In First Out) 형태로 제거된다. 여기서 메소드 호출 시, FILO 형태로 삽입된다는 것의 의미는 메소드가 호출될 때마다 Stack Area 에는 각각 하나의 메소드 정보를 담고있는 Stack Frame 이 생성되는데 구체적인 생성 시기는 메소드의 '{' 를 만났을 때 생성되고 '}' 를 만나면 제거된다.
Stack Area 는 Multi Thread 환경에서 각 Thread 별로 생성된다.
아래는 위의 세 영역에 저장되는 데이터를 조금 더 상세히 설명하기 위해 필자가 직접 작성한 코드와 그림이다. 아래의 그림을 참고하여 Runtime Data Area 를 이해하는데 조금이라도 도움이 되기를 바란다.


Permanent Generation
본 글의 핵심이되는 키워드라고 볼 수 있는 Permanent Generation 이다. Permanent Generation 의 경우 Java 7 까지만 존재했다. 그렇다면 Permanent Generation 의 담당했던 주된 역할은 무엇이었을까?
우선, Permanent Generation 의 경우 개요의 그림을 보면 Heap Area 에 속한 것처럼 그려져있다. Permanent Generation 은 Heap Area 에 속해있지만, Heap Area 와 구분되어 사용된다. 사실 이 부분에 관련해서는 구분하느냐, 구분하지 않느냐에 대한 의견차이가 존재하지만 이 글에서는 구분하는 쪽의 의견으로 기술하도록 하겠다. 이유를 서술하자면, 초심자인 필자의 입장에서는 역할을 구분하여 책임을 부여하는 것이 조금 더 이해하기 쉬웠고 조금 더 이미지를 명확히 떠올릴 수 있었기 때문이다.
각설하고, Heap Area 에 속해있는 것은 사실이니 Permanent Generation 또한 GC 의 영향을 받는다. Permanent Generation 에는 주로 스태틱 멤버 변수 혹은 클래스 멤버 변수라고 불리는 변수, 클래스와 메소드의 메타 데이터가 적재된다.
여기서 한 가지 의문이 들텐데, Permanent Generation 에 적재되는 데이터들이 Static Area 에 적재되는 데이터와 동일하다는 것이다. 필자도 처음 공부했을 때 이 부분이 굉장히 혼란스러웠다. 결론을 바로 말하자면, Static Area 는 Permanent Generation 에 속해있다. 라고 필자는 이해했다.
즉, Heap Area 에 Permanent Generation 이 속하고 Permanent Generation 에 Static Area 가 속한다는 것이다. 위에 기술했듯이 이 글에서는 Heap Area 와 Permanent Generation 을 구분해서 서술하기에 역할을 분리했지만, 만약 구분하지 않는다면 Heap Area 가 Static Area 의 역할까지 담당하게 된다고 생각하면 된다.

필자는 위 그림을 통해 좀 더 손쉽게 이해할 수 있었다.
위 그림을 살펴보면, Permanent Generation 은 JVM 이 사용한다고 되어있는데, 이 말은 즉 Permanent Generation 은 JVM에 의해 크기가 제한된 영역이라는 뜻이다. Java 8 부터 Permanent Generation 을 대신해 Metaspace 가 새롭게 도입된 가장 큰 이유가 이것때문이다.
Metaspace
Permanent Generation 과 함께 본 글의 핵심 키워드라고 볼 수 있는 Metaspace 다. Metaspace 는 Java 8 부터 Permanent Generation 을 대신해 새롭게 도입된 개념이다.
Metaspace 가 새로 도입된 이유는 Permanent Generation 이 JVM 에 의해 크기가 제한된 영역이었던 것 때문이다.
- Exception in thread "main":java.lang.OutOfMemoryError: Java heap space
- Exception in thread "main":java.lang.OutOfMemoryError: PermGen space
- Exception in thread "main":java.lang.OutOfMemoryError: Requested array size exceeds VM limit
- Exception in thread "main":java.lang.OutOfMemoryError: request bytes for . Out of swap space
- Exception in thread "main":java.lang.OutOfMemoryError: Native method
등 위와 같이 메모리 영역 부족으로 인한 굉장히 많은 에러가 발생했다. 그렇다면 새로 도입된 Metaspace 는 제한된 메모리 영역 문제를 어떻게 해결했을까?
Metaspace 는 JVM 의 관리하에 있는 Heap Area 에서 벗어나, OS 에 관리하는 Native Memory 에 속하게 되었다. OS 에서 직접 관리되는 Metaspace 의 경우 JVM 에 의해 관리되던 Permanent Generation 보다 막대한 크기를 할당받게 되었고, 위에 기술한 Permanent Generation 의 제한적인 크기에 의한 문제점들을 해결할 수 있었다.
그렇다면, Permanent Generation 과 Metaspace 의 차이점은 단지 그것뿐일까?


JDK 8 의 공식문서를 확인해보면 해당 내용이 있는 것을 확인할 수 있다.
Hotspot 에서 Permanent Generation 은 제거되고 Permenent Generation 에서 관리하던 클래스 메타 데이터, interned String, 스태틱 멤버 변수는 Heap Area 나 Native Memory 영역으로 옮겨졌다. 더 정확한 표현으로 클래스 메타 데이터는 Native Memory로 interned String과 스태틱 멤버 변수는 Heap Area 로 할당된다.
즉, 기존에 Permanent Generation 에 저장되었던 클래스와 메소드의 메타 데이터는 그대로 Metaspace 에 저장하고 interned String 과 스태틱 멤버 변수는 Heap Area 의 관리하로 들어간다는 것이다. 사실 이 부분이 굉장히 혼란스러울 수도 있다. 아래의 필자가 작성한 그림을 한 번 살펴보자

Java 8 에서의 JVM Runtime Data Area 의 구조는 위와 같다. interned String 이 Heap Area 로 이관됨에 따라 String Constant Pool 또한 Heap Area 에 속하게 됐으며, 스태틱 멤버 변수인 classPrimMemberVariable, classRefMemberVariable, classLiteralMemberVariable 의 메타 데이터는 Metaspace 내부의 Method Area (=Static Area) 에서 관리되는 반면 해당 메타 데이터의 객체와 참조값은 Heap Area 에서 관리되는 것을 볼 수 있다.
결론
Java 8 으로 변경되면서 Permanent Generation 에 적재됐던 interned String 즉 String Constant Pool 이 Heap Area 로 옮겨졌으며,
기존에 Permanent Generation 에 저장되던 클래스 메타 데이터가 Metaspace 로 이동하고 스태틱 변수의 참조 값, 혹은 객체는 Heap Area 에 저장되도록 변경되었다.
Metaspace 에는 여전히 스태틱 변수에 대한 참조를 보관하며 JVM 의 Heap Area 가 아닌 OS 의 관리를 받는 Native Memory 상에서 해당 데이터를 보존한다.
필자는 이번 글을 작성하면서 굉장히 혼란스러웠지만, JVM 의 Runtime Data Area 를 대강이라도 이해했다는 것이 굉장히 기뻤다. Down-Top 보다는 Top-Down 을 선호하던 필자지만, 기존에 작성했던 코드들이 돌아가는 원리를 파악할 수 있었다는 점 그리고 이전에 공부했던 정보들을 다시금 되짚을 수 있었다는 점에서 굉장히 뜻깊은 시간이었다. 이번 글은 다른 기술 블로그를 보고 공부했던 내용들을 정리한 것이다. 필자의 글보다 더 잘 정리해두셨으니 꼭 한 번 참고해보길 바란다.
위에서 기술했던 내용과 동일하게 필자의 글에는 잘못된 정보가 존재할 수 있으며, 그에 대한 피드백은 언제나 환영합니다.
참고 블로그
https://8iggy.tistory.com/229
https://8iggy.tistory.com/230
https://jgrammer.tistory.com/144
https://12bme.tistory.com/382
https://jaemunbro.medium.com/java-metaspace%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-ac363816d35e
'Dev' 카테고리의 다른 글
HTTP (1) | 2024.02.24 |
---|---|
Transaction (0) | 2024.02.20 |
Spring Security Session 과 RestAPI (0) | 2023.08.17 |
In-Memory NoSQL DBMS Redis (0) | 2023.06.18 |
JWT를 사용하여 웹 사이트 공격에 대비하기 (with.Spring Security) (0) | 2023.04.29 |