런타임 데이터 영역(Runtime Data Area)은 실제 클래스 파일이 적재되는 곳으로 JVM이 OS로부터 자바 프로그램 실행을 위한 데이터와 명령어를 저장하기 위해 할당받는 메모리 공간이다. 런타임 데이터 영역은 크게 다섯 가지 영역으로 나뉜다.
- 메서드 영역 (Method Area)
- 힙 영역 (Heap Area)
- 스택 영역 (Stack Area)
- PC 레지스터 (Program Counter Register)
- 네이티브 메서드 스택 (Native Method Stack)
그리고 Java에서 Thread가 공유하는 영역과 공유하지 않는 영역은 다음과 같다.
🫧 Thread가 공유하는 영역
- 힙 영역
- 메서드 영역
🫧 Thread가 공유하지 않는 영역
- 스택 영역
- PC 레지스터 영역
- 네이티브 메서드 스택
🌱 PC 레지스터 (Program Counter Register)
스레드는 스레드마다 각자의 메서드를 실행하는데, 스레드 별로 멀티스레드 환경이 보장되어야 하므로 명령어 주소값을 저장할 공간이 필요하다. 따라서 PC 레지스터 영역에 각 스레드마다 현재 수행 중인 JVM 명령의 주소를 저장하고 관리하여 추적이 가능하게 만들어준다.
이는 OS의 PC 레지스터와 유사한 역할이나, CPU와는 별개로 JVM에서 관리한다.
만약 실행했던 메서드가 native 하다면, 해당 명령어의 위치를 알 수 없기 때문에 PC 레지스터에 undefined 값을 기록한다. 실행했던 메서드가 native 하지 않다면, PC 레지스터는 JVM에서 사용된 명령의 주소값을 저장한다.
🤓 native 하다는 것은?
Java가 아닌 다른 언어(C/C++)로 실행된 메서드를 의미한다.
🌱 네이티브 메서드 스택 (Native Method Stack)
위에서 PC 레지스터에 native 메서드들은 PC 레지스터에 저장이 되지 않는다고 설명했다. 이는 네이티브 메서드들을 위한 영역이 별도로 필요하다는 것을 알 수 있다. 네이티브 메서드 스택 영역이 이 네이티브 메서드들을 실행하기 위한 스택 영역이다.
네이티브 메서드 스택 영역 또한 스택 영역과 마찬가지로 각 스레드마다 개별적으로 생성되며, 자바 외부의 네이티브 코드를 호출할 때마다 생성되었다가 호출 완료 시 소멸된다.
🌱 스택 영역 (Stack Area)
스택 영역은 지역 변수, 매개변수, 메서드 리턴 값, 연산에 사용되는 임시 데이터 등이 저장되는 영역이다. 스택에 저장되는 데이터들은 프레임 구조로 저장된다. 또, 스택 영역은 각 스레드마다 개별적으로 생성되며, 메서드 호출 시 생성되었다가 메서드 종료 시 소멸된다.
🌱 힙 영역 (Heap Area)
- 런타임에 결정되는 참조 자료형이 저장됨
- new 연산자를 통해 생성된 객체(인스턴스)가 저장됨
- 객체가 더 이상 쓰이지 않거나 명시적 null 선언 시 GC가 청소
- 모든 스레드가 공유
🌱 메서드 영역 (Method Area) 혹은 Perm Gen
메서드 영역은 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성되고, 클래스 로더에 의해 로드된 클래스 정보를 저장한다. 즉, 메서드 영역에는 JVM이 읽어 들인 각각의 클래스의 바이트 코드, 상수, 필드, 메서드 등 클래스 정보와 관련된 모든 내용이 저장되는 영역이다.
또한, 메서드 영역은 모든 스레드에서 공유되며, 프로그램 시작부터 종료까지 메모리에 적재되어 있다는 특징을 가지고 있다.
메서드 영역에서 더 이상 데이터를 저장할 공간이 없으면 OutOfMemoryError를 발생시킨다. *(Java 8부터 개선)
✅ 메서드 영역 ⊂ Permanent Generation
메서드 영역은 JVM 벤더마다 다르게 구현되어 있다. 그중 Oracle Hotspot JVM JDK 7까지는 메서드 영역을 Permanent Generation(PermGen)이라고 불리기 때문에 메서드 영역과 혼용되어 쓰인다.
이 Perm Gen은 JDK 8 버전부터는 Metaspace로 완전히 대체되었다.
🤔 메서드 영역의 메모리 관리
자바는 동적 로딩을 활용하기 위해 컴파일 시점에 로딩되는 클래스가 있고, 런타임 시점 혹은 로드 타임에 로딩되는 클래스가 있기 때문에 메서드 영역에 애플리케이션에서 사용되는 모든 클래스의 메타 데이터가 저장되는 것은 아니다.
따라서 메서드 영역에 저장되어 있는 클래스의 메타 데이터가 힙 영역에 있는 객체들과 연결되어 있지 않은 데이터라면(GC가 힙 영역에서 객체의 메모리를 회수하고 해당 클래스의 인스턴스가 Heap 영역에 없을 때) 메서드 영역에 있는 클래스의 메타 데이터가 삭제된다.
다만 공식 문서에 따르면, JVM 벤더에 따라 메서드 영역에서 GC가 동작할 수도 있고 하지 않을 수도 있다. 즉 메서드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.
🤔 왜 Permanent Generation이 Meataspace로 바뀐 거야?
JDK 7 메모리 구조 (PermGen)
JDK 8 메모리 구조 (Metaspace)
이 Perm Gen 영역이 JDK 8부터는 Metaspace의 완전히 대체되었다.
기존 Perm Gen은 JVM에 의해 크기가 제한된 영역으로 유지되었다. 따라서 영역 제한으로 인한 메모리 범위 초과(OutOfMemoryError) 문제가 있었다. 또, 크기가 작아 비용이 많이 드는 가비지 컬렉션을 수시로 수행해 필요한 공간을 확보해야 했고, 클래스 로더들이 제대로 GC 되지 않는 문제로 인해 memory leak이 발생하곤 했다.
static int i = 1; // 1. primitive 타입으로 선언된 static field
static void a(){} // 2. static method
static method static A a = new A(); //3. reference 형식의 static field (static object)
static은 위와 같은 방식으로 사용될 수 있는데, primitive 타입의 static variables method, reference 형식의 Object는 permanent 영역에 저장된다.
static A a = new ArrayList<A>();
a.add(new A());
a.add(new A());
a.add(new A());
문제는 위와 같이 ArrayList 같은 레퍼런스 타입의 동적 배열 객체를 static으로 생성하면 레퍼런스를 Perm Gen 영역에 저장하는 데 해당 객체 배열에 객체 원소를 추가하면 static object의 레퍼런스가 Perm Gen 영역에 쌓이게 되는 이슈가 생긴다. 그뿐 아니라 String literal data를 저장하던 String pool도 Permanent 영역에 저장하느라 OOM(Out Of Memory) 에러가 발생하는 이슈가 잦았다.
따라서 Perm Gen이 Metaspace라는 영역으로 대체되어 기존 Java Heap 메모리 옆에 붙어있지 않고, OS에서 제공하는 native 메모리를 사용하며, 필요시 자동으로 크기를 증가시켜 공간을 확보할 수 있게 되었다.
이러한 변경사항으로 클래스 메타데이터 사용량이 Metaspace의 최대 크기에 도달할 때 자동으로 GC가 돌게 되면서 JVM은 OutOfMemoryError의 발생확률을 줄일 수 있다.
Java 8 관련 ORACLE 문서에서 interned Strings와 클래스 정적 변수들을 Heap 영역으로 보낸다는 내용을 볼 수 있다.
Class metadata, interned Strings and class static variables will be moved from the permanent generation to either the Java heap or native memory.
The code for the permanent generation in the Hotspot JVM will be removed.
Hotspot JVM에서 permanent generation은 제거되고, perm gen에서 관리하면 class meta data는 native memory 영역으로 옮겨지고 interned String, class static variable은 heap 영역으로 옮겨진다.
The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap.
Allocation of new class meta-data would be limited by the amount of available native memory rather than fixed by the value of -XX:MaxPermSize, whether the default or specified on the command line.
클래스 메타 데이터를 Native Memory 영역에 할당하고, interned Strings와 클래스 정적 변수들을 Heap 영역으로 보낸다.
따라서 새로운 클래스 메타 데이터의 할당은 기존의 Permanent 영역처럼 고정된 크기가 아닌 사용 가능한 Native Memory 영역에 의해 제한된다.
JEP 122: Remove the Permanent Generation
JEP 122: Remove the Permanent Generation OwnerJon MasamitsuTypeFeatureScopeImplementationStatusClosed / DeliveredRelease8Componenthotspot / gcDiscussionhostspot dash dev at openjdk dot java dot netEffortXLDurationXLBlocksJEP 156: G1 GC: Reduce need
openjdk.org
또, 오라클 버그 레포트에서도 관련 업데이트를 찾을 수 있다.
Bug ID: JDK-6962931 move interned strings out of the perm gen
bugs.java.com
JDK8 레퍼런스에 나온대로 "static 변수를 heap으로 보낸다"는 것은 GC의 대상이 될 수 있다는 것을 의미하는데 static 변수는 클래스 변수이기 때문에 명시적 null 선언이 되지 않으면 GC 되어서는 안 되는 변수이다.
❗️ 클래스 변수가 GC 대상이 된다고??
이는 Method 영역이 클래스(static) 변수를 저장한다고 이해하는 시점에서 오해가 발생하게 되는데 Method 영역은 class의 메타데이터를 저장할 뿐 실질적인 객체와 데이터가 저장되었던 것이 아니다.
공식 문서에 static 변수(primitive type, interned string)를 heap으로 보낸다고 했지만 해당 변수의 참조는 여전히 Metaspace 영역에 저장되어 있다. 따라서 참조를 잃은 static 변수들만 GC의 대상이 될 수 있다.
→ 클래스 변수 및 객체의 저장 위치와 클래스 메타 정보의 위치가 Perm Gen에서 Heap과 Metaspace로 서로 분리되었다.
런타임 상수 풀 (Runtime Constant Pool)
런타임 상수 풀은 클래스 로더가 메서드 영역에 클래스를 로딩할 때, 같이 메서드 영역에 적재된다. 즉, 클래스 별로 각각 따로 런타임 상수 풀을 가지고, 해당 클래스의 상수들이 이 풀에 저장된다. 런타임 상수 풀에는 클래스 및 인터페이스의 상수뿐 아니라 메서드와 필드에 대한 모든 레퍼런스 정보를 갖고 있다.
그리고 런타임 상수 풀 영역은 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다.
🤔 상수 풀이라는 것도 있던데 런타임 상수 풀이랑 다른 건가?
상수 풀(Constant Pool)과 런타임 상수 풀(Runtime Constant Pool)은 다른 개념이다.
자바는 이진 형식의 클래스 파일로 컴파일된 프로그램을 저장하는 데, 클래스의 메타데이터와 코드를 저장하는 데 사용된다. 클래스 파일은 여러 섹션으로 나뉘는데, 그중 하나가 Constant Pool 테이블이다.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello!!");
}
}
Hello.class:
Magic: 0xCAFEBABE
Minor Version: 0x0000
Major Version: 0x0034
Constant Pool Count: 0x000a
Constant Pool:
#1 = Utf8 "Hello"
#2 = Class #1
#3 = Utf8 "java/lang/Object"
#4 = Utf8 "main"
#5 = Utf8 "([Ljava/lang/String;)V"
#6 = Utf8 "args"
#7 = Utf8 "[Ljava/lang/String;"
#8 = Utf8 "System"
#9 = Utf8 "out"
#10 = Utf8 "Ljava/io/PrintStream;"
위 클래스를 컴파일한 후 클래스 파일을 열어보면 의 상수 풀이 다음과 같이 저장되어 있는 것을 볼 수 있다.
상수 풀은 클래스 파일 내에서 상수들을 저장하는 테이블로, 클래스의 메타데이터, 리터럴 값, 메서드 및 필드에 대한 참조 등을 포함한다. 이 테이블은 클래스 파일의 로딩 과정에서 읽히고 해석된다.
한편, 런타임 상수 풀은 JVM이 클래스를 로드하고 실행하는 동안 메모리에 생성되는 상수 풀을 의미한다. 클래스 파일의 Constant Pool 테이블 내의 정보가 런타임 상수 풀에 로드되어 사용된다. 클래스 파일의 Constant Pool 테이블이 런타임 상수 풀을 구성하는 데 사용되는 데이터를 포함한다고 생각할 수 있다.
- 상수 풀(Constant Pool): Class File
- 상수 풀은 클래스 파일의 일부로, 컴파일된 자바 클래스 파일에 포함된다.
- 클래스 파일에는 클래스의 메타데이터뿐만 아니라, 클래스에 대한 리터럴 값, 메서드 및 필드에 대한 참조 등 모든 상수들이 상수 풀에 저장된다.
- 상수 풀은 클래스 파일의 구조 중 하나이며, 클래스 파일이 로드될 때 메모리에 적재된다.
- 클래스 파일이 로드될 때 상수 풀은 불변하며, 클래스의 생명주기 동안 변경되지 않는다.
- 런타임 상수 풀(Runtime Constant Pool): JVM
- 런타임 상수 풀은 JVM(Java Virtual Machine)이 실행되는 동안 메모리에 생성되는 공간이다.
- 클래스를 JVM이 로드할 때 클래스 파일의 상수 풀에서 필요한 정보들이 런타임 상수 풀로 복사된다.
- 런타임 상수 풀은 클래스 파일의 상수들을 저장하고, 클래스의 인스턴스화 및 메서드 호출과 같은 런타임 동작에 사용된다.
- JVM이 실행되는 동안 런타임 상수 풀은 계속 유지되며, JVM이 종료되면 메모리에서 해제된다.
즉, 상수 풀은 클래스 파일에 저장되어 클래스의 구조를 정의하고, 런타임 상수 풀은 실행 중에 클래스의 상수들을 저장하고 사용한다. 상수 풀은 클래스 파일의 구성 요소이며, 런타임 상수 풀은 JVM이 실행되는 동안 클래스의 상수들을 관리하는 데 사용된다.
🤔 그럼 Runtime Constant Pool 과 String Pool 은 다른 건가?
이 둘은 관련은 있지만 서로 다른 개념이다. 자바에서는 문자열 리터럴을 저장하는 독립된 영역을 String Constant Pool 또는 String Pool 이라고 부른다. String은 불변객체이기 때문에 문자열 생성 시 이 String Constant Pool에 저장된 리터럴을 재사용해 메모리를 절약할 수 있다.
String str1 = "madplay"; // String Constant Pool 이라는 영역에 저장해 스트링 상수값으로 관리
String str2 = "madplay"; // 기존 "madplay" 상수 재활용. str 1이랑 같은 메모리 참조
String str3 = new String("madplay"); // 다른 객체와 마찬가지로 Heap 영역에 할당
String str4 = new String("madplay"); // 새로운 객체를 생성. str3이랑 다른 메모리 참조
System.out.println(str == str2); // true (같은 객체를 재사용하기 때문에)
System.out.println(str == str3); // false
System.out.println(str.equals(str3)); // true
여기서 말하는 리터럴이 정확하게 무엇일까?
Constant Pool에 저장되는 상수와 String Constant Pool에 저장되는 리터럴은 어떤 차이점이 있을까?
상수는 '초기화 이후 값이 변하지 않는 수'를 의미하고, 리터럴은 상수의 일종이지만, 선언 없이 바로 사용할 수 있는 문자 그대로의(=리터럴 한) 상수를 의미한다. 이렇게만 설명하면 아직 헷갈리니 예시를 들어 둘을 구분하자면
var a = 10; // a는 변수, 10은 리터럴이다.
var name = "Simba"; // name은 변수, "Simba"는 리터럴이다.
const pi = 3.14; // pi는 상수, 3.14는 리터럴이다.
const DRINKING_AGE = 21;
const VOTING_AGE = 18;
// 18과 21은 리터럴이다.
// 리터럴은 if(age > 18) 또는 if(age < 21)과 같이 프로그램의 모든 영역에서 사용될 수 있다.
// 하지만 상수를 이용하면 if(age > VOTING_AGE)와 같이 코드를 더 이해하기 쉽게 만들 수 있다.
이렇게 구분할 수 있다. Java는 기본 자료형을 제외한 객체를 만들 때, new 키워드를 이용해 참조형으로 만든다. 하지만, String은 예외적으로 new 키워드 없이 객체를 만들 수 있다. 이를 문자열 리터럴 생성 방식이라고 한다.
JEP 122: Remove the Permanent Generation
JEP 122: Remove the Permanent Generation OwnerJon MasamitsuTypeFeatureScopeImplementationStatusClosed / DeliveredRelease8Componenthotspot / gcDiscussionhostspot dash dev at openjdk dot java dot netEffortXLDurationXLBlocksJEP 156: G1 GC: Reduce need
openjdk.org
What is the difference between PermGen and Metaspace?
Until Java 7 there was an area in JVM memory called PermGen, where JVM used to keep its classes. In Java 8 it was removed and replaced by area called Metaspace. What are the most important differ...
stackoverflow.com
JVM: Runtime Data Area
Java Virtual Machine(JVM) 구조는 크게 Class Loader, Runtime Data Area, Execution 이렇게 세 가지로 나눌 수 있는데, 여기서는 메모리 영역인 Runtime Data Area에 대한 내용을 다룹니다. Runtime Data Area Runtime Data Area란,
seunghyunson.tistory.com
Java 런타임 데이터 영역
읽기 전 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다. 개인적으로 사용해보면서 배운 점을 정리한 글입니다. 문제 제기 변수 저장에 대해 찾아보다 보면 constant pool이라
8iggy.tistory.com
[JAVA] Java8부터는 static이 heap영역에 저장된다?
static은 런타임시 클래스 로더에 의해 메서드 영역에 적재되며 프로그램이 종료될 때 까지 GC에 대상이 아니라고 알고있었다. 그런데, permanent영역과 metaspace에 관련된 글을 읽는 중 static이 heap영
jgrammer.tistory.com
JDK 8에서 Perm 영역은 왜 삭제됐을까
johngrib.github.io
JVM 메모리 구조
JVM(Java Virtual Machine) JVM은 자바 가상머신의 약자로, 자바 소스코드(.java)로부터 만들어지는 자바 바이너리 파일(.class)을 실행할 수있습니다. 또한 JVM은 플랫폼에 의존적입니다.(리눅스의 JVM과 윈
hw55.tistory.com
[Java] 런타임 데이터 영역(Runtime Data Area)에 대해
자바 가상 머신(JVM)의 런타임 데이터 영역에 대해 알아보자
velog.io
[Java] 많이 헷갈려하는 String constant pool과 Runtime Constant pool, Class file constant pool
String Constant Pool과 Constant Pool 이 두 가지는 완전히 다른 개념입니다. 용어가 비슷한 형태이기 때문에 이 두 가지를 혼용하여 헷갈리는 경우가 많습니다만, 저장되는 위치부터 저장하는 데이터의
deveric.tistory.com
Confusion between constants and literals?
I am currently reading about constants on the c++ tutorial from TutorialsPoint and, where it says: Constants refer to fixed values that the program may not alter and they are called literals. (
stackoverflow.com
Constant vs Literal - Difference and Comparison | Diffen
Software programmers use various data types in their code. A literal is a value that is expressed as itself. For example, the number 25 or the string "Hello World" are both literals. A constant is a data type that substitutes a literal. Constants are usefu
www.diffen.com
Why concatenation of String object and string literal is created in heap?
I have below Strings String str1 = "Abc";//created in constant pool String str2 = "XYZ";//created in constant pool String str3 = str1 + str2;//created in constant pool String str4 = new String("P...
stackoverflow.com
Where does Java's String constant pool live, the heap or the stack?
I know the concept of a constants pool and the String constant pool used by JVMs to handle String literals. But I don't know which type of memory is used by the JVM to store String constant literal...
stackoverflow.com
'Study' 카테고리의 다른 글
[오픈소스] Spring Boot 프로젝트 컨트리뷰터 되기 (0) | 2024.04.29 |
---|---|
[Java] Method Area는 Heap 영역이 아니다! (1) | 2024.02.19 |
[Java] 변수와 객체 데이터 저장 (0) | 2024.02.17 |
[DB] 트랜잭션의 격리 수준(Isolation Level)이란? (0) | 2024.02.16 |
[Java] 객체비교 시 equals()와 hashcode() 둘 다 재정의해야 하는 이유 (0) | 2024.02.13 |