22/09/29 - 게시글 등록
22/10/13 - PermGen에 관한 Heap 영역 수정 및 추가
Part1에서는 JVM이 무엇인지,
Part2에서는 Execution Engine(실행엔진),
Part3에서는 ClassLoader(클래스로더),
이번 Part4에서는 Runtime Data Area 부분을 살펴보겠다.
Part3에서 우리는 클래스 파일(.class)들이 어떻게 JVM에 탑재되는지 과정을 살펴보았다.
그렇다면 JVM에 탑재되는 이 클래스 파일들은 어디에 저장되는 걸까?
💻 Runtime Data Area 간략 소개
JVM 메모리 또는 메모리 영역이라고 불리는 Runtime Data Area는 프로그램을 수행하기 위해 OS로부터 메모리를 할당받는 영역이다. 이 할당 받은 메모리를 용도에 따라 영역을 구분하여 관리하는데 크게 5가지 영역이 있다.
이 영역에서는 클래스로더에서 분석된 클래스 파일의 데이터를 저장하고 실행 도중에 필요한 데이터도 저장하게 된다.
그럼 좀 더 자세히 알아보도록 하자.
Method Area (메소드 영역)
- 인스턴스 생성을 위한 객체 구조, 생성자, 필드 등 저장됨
- Runtime Constant Pool, static 변수, 메소드 데이터와 같은 Class 데이터도 관리됨
- JVM 1개당 하나만 생성됨
- 인스턴스 생성에 필요한 정보도 존재하기에, JVM의 모든 쓰레드들이 공유하여 사용
- JVM의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면, 실제 물리 메모리 주소로 변환해서 전달해줌
- 기초 역할을 하므로, JVM 구동 시 생성되며 종료 될 때까지 유지되는 공통 영역
정리해보자면 처음에 클래스로더의 Loading 과정에서 클래스 파일을 바이너리 데이터로 변환하여 이를 메소드 영역에서 저장한다. 이 Loading이 끝나면 해당 클래스 타입의 객체를 생성하여 메모리의 Heap 영역에 저장하게 된다.
메소드 영역은 JVM 벤더마다 다양한 형태로 구현될 수도 있고, 오라클 핫스팟 JVM에서는 Permanent Area 또는 Permanent Generation(PermGen)이라고도 불린다. 이 메소드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.
Oracle HotSpot JVM에서는 Method Area가 Permanent Generation이라고 불리는데, 이 때 추후에 언급할 Heap 영역에서 소개된다. 따라서 오라클에서는 Method Area와 Heap Area 에서 소개하는 PermGen은 동일하다고 보면 된다. 그렇기 때문에 "PermGen은 힙인가 아닌가?"라고 생각하여 혼란스러울 수 있는데 이는 설명이 길어질 것 같아서 마지막부분에서 다시 언급하겠다.
Runtime Constant Pool (런타임 상수 풀)
- 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역
- 메소드 영역에 포함되어 있지만, JVM 동작에서 가장 핵심적인 역할을 수행하기에 JVM 명세에서도 따로 중요하게 기술함
- 각 클래스와 인터페이스의 상수뿐만 아니라, 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블
- 어떤 메소드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리상 주소를 찾아서 참조함
Heap Area(힙 영역)
- 코드 실행을 위한 Java로 구성된 객체 및 JRE 클래스들이 탑재됨
- String Pool, 실제 데이터를 가진 인스턴스, 배열 등 저장됨
- JVM 1개당 하나만 생성
- 이 영역이 가진 데이터는 모든 Stack Area에서 참조되어 쓰레드 간 공유됨
- 참조되지 않는 인스턴스와 배열에 대한 정보도 얻을 수 있기때문에 GC의 주 대상이 됨
- 힙에 저장 및 할당된 메모리 회수 권한은 GC만 있으며, 없애려면 JVM을 종료해야함
- 이 영역에 있는 오브젝트들을 가리키는 참조 변수가 Stack Area에 올라가게 됨
- JVM 성능 등의 이슈에서 가장 많이 언급되는 공간으로 힙 구성 방식이나 GC 방법 등은 JVM 벤더의 재량
힙 영역에서 인스턴스가 생성된 후 시간에 따라서 4가지로 나눌 수 있다.
Eden, Survivor0, Survivor1, Old로 나누어진다.
한 번 더 말하지만 메소드 영역은 오라클 핫스팟 JVM에서 Permanent라고 불리고 있다.
Java Heap같은 경우 Eden, S0, S1, Old 이렇게 구분될 수 있는데 크게 다음 두가지로 보고있다.
- Young Generation
- 생명 주기가 짧은 "젊은 객체"를 GC 대상으로 하는 영역
- 해당 영역에서 발생되는 GC를 Minor GC라고 하며, Major GC에 비해 속도가 빠름
- Old Generation
- 생명 주기가 긴 "오래된 객체"를 GC 대상으로 하는 영역
- 해당 영역에서 발생되는 GC를 Major GC라고 하며, Minor GC에 비해 속도가 느림
- Permanent Generation
- 클래스와 메소드의 메타데이터를 담는 곳
- Old Gen에서 살아남은 객체가 오는 곳이 아니라 별도의 힙 공간이라고 생각하면 됨
- 기본값으로 제한된 크기를 가지는 단점이 있어서 Java 8 이후 Native 영역에 존재하는 Metaspace 영역으로 대체됨
이 힙 영역은 모든 쓰레드가 공유하므로 속도가 조금 느린 점이 있다. 거기다가 동시성 문제도 발생할 수 있다. 쓰레드에 의해서 공유가 되기 때문에 Thread Safe 하지 않는다. 이 때문에 해당 영역에 있는 객체나 인스턴스를 사용하게 되면 synchronized 블록을 사용하는 방법 등을 비롯하여 동시성을 지켜 주는 방법을 사용해야 한다.
Thread Safe는 멀티 쓰레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 쓰레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 보다 엄밀하게는 하나의 함수가 한 쓰레드로부터 호출되어 실행 중일 때, 다른 쓰레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 쓰레드에서의 함수의 수행 결과가 올바르게 나오는 것으로 정의한다.
JVM Performance Optimizing 및 성능분석 사례라는 책에 따르면 핫스팟 JVM 구조는 위 그림처럼 바뀌었다.
- Java Heap : JVM이 관리하는 영역
- Native Memory : OS에서 관리하는 영역
Perm은 클래스와 메소드 메타데이터, Static 변수와 상수 정보들이 저장되는 공간이었다.
이 영역은 Java 8부터 Metaspace로 변경되면서 Native 영역으로 이동하였다.
다만 Static 변수와 상수들은 Java Heap 영역에 옮겨져서 GC의 대상이 된다.
그렇다면 왜 바뀌게 되었을까?
- Perm의 고정된 크기로 데이터가 넘치면 에러 발생
- 동적으로 클래스 생성하므로 Perm 최대 사이즈를 변경한다해도 해결 X
이러한 단점때문에 다음과 같이 변경되었다.
- 필요한만큼 크기 늘릴수 있음
- OS가 자동 크기 조절
즉, Perm 영역의 사이즈 제한을 없애고 동적으로 늘릴 수 있게 된 것이다.
이로써 개발자는 Perm 영역을 크게 의식할 필요가 없어진다.
Heap 영역에서 Perm을 같이 보여준 것은 JVM 메모리 개념과 Generation(세대)를 설명하기 위한 것이고, JVM을 구현에서는 Perm은 Java Heap으로 간주하지 않는다. 왜냐하면 최대 힙 사이즈에도 포함되지 않고, Old에서도 승격되지 않기 때문이다. 따라서 별도의 힙 공간이라고 생각하면 된다. (관련자료1)(관련자료2)
PermGen (Permanent Generation) is a special heap space separated from the main memory heap.
PermGen은 메인 메모리의 힙과 분리된 특수한 힙 공간이다.
(관련자료3 중 일부)
Stack Area(스택 영역)
- 각 쓰레드 별로 할당되는 영역 (쓰레드당 하나씩 생성)
- Heap 메모리 영역보다 비교적 빠름
- 각각의 쓰레드 별로 메모리를 따로 할당하므로 동시성 문제에서 자유로움
- 메소드를 호출할 때마다 Frame 추가(push), 메소드 종료되면 Frame 제거(pop)
- Frame은 메소드에 대한 정보를 가지고 있는 Local Variable, Operand Stack, Constant Pool Reference로 구성됨
Local Variable
메소드 안의 지역 변수들을 가지고 있음
class Test {
public int hello(int a, double b, String c) {
return 0;
}
}
위 자바코드에서 hello() 메소드안에 지역 변수 배열은 다음과 같이 만들어진다.
- reference는 heap 영역의 레퍼런스(참조)를 의미한다.
- primitive (기본형) 타입은 값을 바로 프레임에 저장된다.
- 그렇기 때문에 기본형 타입인 int, double이 참조 타입인 Integer, Double보다 조금 더 빠르다.
- double과 long은 두 칸씩 차지한다.
Operand Stack
메소드 내 연산을 위해서 바이트 코드 명령문들이 들어있는 공간
Constant Pool Reference
Constant Pool 참조를 위한 공간
Native Method Stack
- 각 쓰레드 별로 할당되는 영역 (쓰레드당 하나씩 생성)
- Native Method를 다루는 영역
- C Stack 이라고도 불리기도 함
- Stack Area와 비슷하게 Native Method가 실행될 경우 Stack에 해당 메소드가 쌓임
Native Method는 Java가 아닌 다른 프로그래밍 언어로 작성된 메소드이다. 즉, JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C스택 또는 C++스택이 생성됨
PC(Program Counter) Register
- 각 쓰레드 별로 할당되는 영역 (쓰레드당 하나씩 생성)
- 자바에서 쓰레드는 각자 메소드를 실행하게 되는데, 이 때 쓰레드별로 동시에 실행하는 환경이 보장되어야 하므로 JVM에서는 현재 수행 중인 명령어 주소값을 저장할 공간이 필요한데, 이 부분을 PC Register 영역이 관리하여 추적함
- 네이티브 메소드를 실행했다면 undefined가 기록되고, 아니라면 JVM에서 사용된 명령의 주소값을 저장함
- PC Register는 JVM에서 사용된 명령의 주소값을 저장
- Register-Base가 아닌, Stack-base로 작동
- 스택에서 Operand를 뽑아서 PC Register에 저장
🖥 마무리 정리
자바 애플리케이션이 실행되면 JVM은 Runtime Data Area에 OS로부터 메모리를 할당받는다.
할당받은 메모리를 크게 Method Area, Heap, Stack, PC Register, Native Method Stack로 구분하여 관리한다.
Method Area와 Heap은 모든 Thread가 공유하고,
Stack, PC Register, Native Method Stack은 Thread 별로 하나씩 존재한다.
- Method Area : JVM이 읽어들인 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메소드 정보, Static 변수, 메소드의 바이트 코드 등을 보관하는 영역
- Heap : 인스턴스 또는 객체를 저장하는 공간
- Stack : JVM 내에서 메소드가 수행될 때마다 하나의 스택 Frame이 생성되어 해당 쓰레드의 JVM 스택에 추가되고 메소드가 종료되면 스택 Frame이 제거됨
- PC Register : 현재 수행 중인 JVM 명령의 주소를 가짐
- Native Method Stack : 자바 외의 언어로 작성된 네이티브 코드를 위한 스택
참고
https://nyximos.tistory.com/41
https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/
'프로그래밍 > Java' 카테고리의 다른 글
자바에서 String을 조심해야하는 이유 (0) | 2022.10.22 |
---|---|
JVM(자바가상머신)이란? - Part5, Garbage Collection (1) | 2022.10.15 |
[Java] sort와 parallelSort 비교 (0) | 2022.09.18 |
Java - 값이 null,공백 등 Blank인지 확인하기 (0) | 2022.04.07 |
Map 인터페이스 (0) | 2022.02.24 |