Java Runtime Data Area

프로세스 전역 공유 영역
1. Heap area (프로세스 전역)
- 로드 후, JVM이 클래스 형식의 객체를 생성한다. 객체는 힙 영역의 메모리 할당
- 메서드 호출 시 지역 변수, 파라미터 중 Reference Type이 존재하는 경우, 힙 영역에 메모리가 할당된다.
- Stack > Stack Frame > Local Variables 배열 에서 힙 영역 메모리 참조
- Primitive Type의 경우 힙 영역이 할당 되지 않으며, 값이 그대로 들어간다.
- Stack > Stack Frame > Local Variables 배열 에서 힙 영역 메모리 참조
- Static Object, String Constant Pool(Interned String) 역시 힙 영역에 존재한다.
- HotSpotJVM에서 JAVA 7 이전에는 PermGen에 존재
- JAVA 8 이후에는 힙 영역에 존재한다.
- GC의 대상이 되는 영역이다.
2. Method area (= Static area = Class area = Code area) (프로세스 전역)

- HotspotJVM에서 JAVA 7 이전 Method Area 명칭 : PermGen (Permanent Generation)
- HotspotJVM에서 JAVA 8 이후 Method Area 명칭 : MetaSpace
- 클래스 파일의 정보와 상위 클래스의 정보, 수정자, 변수의 정보, 인터페이스인지 클래스인지 정보를 2진수로 로드한다.
- 동일 클래스의 객체 별 메서드 정보는 동일하므로, 메서드 영역에 저장 및 참조
- 클래스 메타 데이터는 GC의 대상이 아니므로, 대부분의 경우 프로그램 시작부터 끝까지 메모리에 상주하며 이동하지 않는다.
- 특정 클래스 로더가 로드한 모든 클래스 / 인터페이스가 모두 참조되지 않는 경우, 클래스 로더 단위의 언로드가 이론상 가능하지만, 일반적인 경우 언로드 되지 않는다.
3. Code Cache (프로세스 전역)
- JIT Compiler에 의해서 컴파일된 네이티브 코드를 저장하는 영역이다.
- Heap이나 Metaspace 내부에 존재하는 것이 아닌, Native Area 내부에 독립적으로 존재하는 영역이다.
- https://stackoverflow.com/questions/54840866/does-the-jvm-code-cache-area-exist-in-heap-or-metaspace
- JVM 옵션 -XX:-TieredCompilation 적용 시, Tiered Compilation이 비활성화 되며, Code Cache 크기는 48MB로 줄어들게 된다.
- -XX:InitialCodeCacheSize 을 통해 기본 Code Cache 크기 조정이 가능하다.
- -XX:ReservedCodeCacheSize 을 통해 최대 Code Cache 크기 조정이 가능하며, 최대로 설정할 수 있는 크기는 위 표와 같다.
- -XX:CodeCacheExpansionSize 을 통해, Code Cache가 기본 크기만큼 찼을 때, 크기를 증가시키는 단위를 설정할 수 있다.
- k(킬로바이트), m(메가바이트), g(기가바이트)
- 크기 조정은 백그라운드에서 동작하며, 애플리케이션 실행 성능에 영향을 주지 않는다.
- Code Cache가 가득 차면 JIT는 컴파일러 스레드를 중지하고 인터프리터 코드를 더 이상 최적화하지 않는다.
- 따라서 대규모 애플리케이션을 실행할 때는, 위 JVM 옵션들로 Code Cache 튜닝 필요
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
https://docs.oracle.com/en/java/javase/16/docs/specs/man/java.html
쓰레드 별 공유 영역 (쓰레드 각각에 존재)
1. Java Virtual Machine Stack (쓰레드별)
- 쓰레드의 수행 정보를 기록한다. 메서드 호출 시마다 Stack Frame이 생성되며, JVM에 의해서 Java Stack에는 스택 프레임들이 적재된다.
- Stack Trace나 Stack Dump를 통해 쓰레드의 활동 추적 시 표시되는 정보들이 바로 자바 스택의 스택 프레임 정보들이다.
- 쓰레드가 쓸 수 있는 스택 크기를 넘기면, StackOverflowError 발생
- 스택 사이즈 동적 확장 시 메모리가 부족하거나, 새로운 쓰레드에 대한 스택을 만들 때 메모리가 부족하면 OutOfMemoryError 발생
- push, pop 두 가지 연산만 수행됨
- 현재 실행되는 메서드의 스택 프레임이 스택의 top에 위치한다.
https://johngrib.github.io/wiki/jvm-stack/
https://wogh8732.tistory.com/88
2. Native Method Stacks (쓰레드별)
- Java 외의 언어로 작성된 프로그램 (C, C++), API Toolkit을 Java에서 사용 및 호출하기 위해 JNI(Java Native Interface)라는 표준 규약 제공.
- Native Method를 사용하기 위한 Stack이다. 언어별로 생성된다.
- Native Method의 경우 native 키워드가 붙는다.
- Native Method를 사용하지 않는다면, 해당 언어에 대한 Native Method Stack을 만들지 않는다.
- 참고https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf
- An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional THE STRUCTURE OF THE JAVA VIRTUAL MACHINE Frames 2.6 15 stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
- Native Method가 종료되면 Java Virtual Machine Stack에 새로운 Frame이 생성 및 실행된다. Native Method를 호출한 Stack Frame으로 돌아가지 않는다.
3. PC Register (쓰레드별)
- JVM의 PC Register는 Register 기반이 아닌, Stack 기반으로 구동된다. (Stack Based VM)
- CPU에 존재하는 Register과는 다르며, 프로그램 실행 시 JVM에 할당된 별도의 메모리 공간(Runtime Data Area) 내부에 존재하는 영역이다.
- 실행 중인 메서드가 native 메서드가 아니라면, 현재 명령어에 대한 주소를 pc register에 담고, 실행 중인 메서드가 native 메서드라면 undefined 상태가 된다.
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
https://en.wikipedia.org/wiki/Stack_register
https://dev.to/deepu105/visualizing-memory-management-in-jvm-java-kotlin-scala-groovy-clojure-19le
https://loosie.tistory.com/846
https://seunghyunson.tistory.com/23
https://naruu098.tistory.com/76
Stack Frame
- 메서드 호출 시마다 생성 및 관리된다. 스택 프레임은 Java Stack에 적재된다.
- 메서드의 반환 주소, 메서드로 넘어온 매개변수, 메서드 내부 지역변수를 포함한다.
- 스택 프레임의 크기는 바이트 코드로 컴파일하는 시점에 이미 결정된다. (불변 크기) 그 이유는 소스 코드 내부에 사용하는 연산과 파라미터 및 변수 정보, 반환 타입이 이미 정의되어 있기 때문이다.
- 현재의 메서드가 에러 없이 정상적으로 종료되면, 리턴 값을 호출자로 넘긴다. 현재 스택 프레임은 사라지며, 리턴 값은 호출자의 Operand Stack에 쌓이게 된다.
Stack Frame 구성요소
1. Local Variables : 지역변수, 파라미터 및 this를 저장하는 배열
- index 0번부터 시작하며 static 메서드를 제외하면, 모든 메서드는 this라는 지역변수를 가진다. this는 현재 객체를 의미한다.
- Primitve Type (int, double 등)은 값을 프레임에 저장한다.
- Reference Type (Integer, Double 등)은 heap의 referecne를 프레임에 저장한다.
- → Primitive type이 Reference Type보다 빠르다.
2. 피연산자 스택 (Operand Stack) : 각각의 스택 프레임에서 메서드 내 계산을 위해서 사용
3. Frame Data : 상수 풀 참조 정보, 정상 종료 시 정보, 비정상 종료 시 exception 정보 포함
3 - 1. Constant Pool Resolution
- 각각의 스택 프레임은 상수 풀에 대해 reference를 가짐
- Constant Pool Resolution은 현재 쓰레드에서 상수 풀에 접근할 수 있는 포인터 정보이다.
- Runtime Constant Pool은 Method Area 내부에 존재한다.
- Dynamic Linking을 지원하기 위함이다. .class 파일(바이트 코드) 는 런타임에 Symbolic Reference를 통해 호출되는 메서드와 액세스할 변수를 참조한다. 런타임에 메서드, 필드 참조를 연결하는 것을 Dynamic Linking이라고 한다.
- Dynamic Linking 시, 아직 정의되지 않은 symbol들을 해결하기 위해 클래스들을 로드하고, 변수들의 런타임 위치와 관련하여 스토리지 구조 내에서 적절한 offset을 통해 변수 접근을 해석한다. ⇒ lazy resolution
- Symbolic Reference는 실제 물리주소와 연결되며, 이러한 작업이 바로 Resolution이다. (Symbolic Reference -> Direct Reference)
- 초기 resolution이 필요한 경우(static, static final)에는 클래스 로더에 의해서 resolution 발생하며, 그렇지 않은 경우(인스턴스 메서드 호출)에는 런타임에 JVM 런타임 시스템에 의해 lazy resolution이 발생한다.
3 - 2. Instruction Pointer :
- 현재 메서드(Stack Frame)를 호출한 Stack Frame의 주소가 Instruction Pointer에 저장된다.
- 메서드의 실행이 끝나면 Intruction Pointer가 가리키는 주소를 PC Register에 설정하고, Java Virtual Machine Stack에서 현재 스택 프레임을 Pop 한다.
- 만약 현재 스택 프레임에 반환 값이 존재한다면, 반환 값을 호출 프레임의 연산자 스택에 쌓는 작업도 병행한다.
- Exception 발생 시 이를 처리하기 위한 Exception Table의 reference 정보도 가진다. 예외 발생 시, JVM은 catch문의 바이트 코드로 점프한다.
https://normaldy.tistory.com/4
https://rxdcxdrnine.tistory.com/23
Assembly Stack Pointer
- 운영체제에 따라서 명칭이 다를 수 있다.
- 12bit OS → SP, BP, IP
- 32bit OS → Extended 접두사. ESP, EBP, EIP
- 64bit OS → Register 접두사. RSP, RBP, RIP
- RSP (Register Stack Pointer) : 스택 포인터. 스택의 최상단을 가리킨다.
- RBP (Register Base Pointer) : 베이스 포인터. 스택의 최하단을 가리킨다.
- RBP는 고정되어 있으며, RSP는 스택에 변경이 발생할 때마다 움직인다.
- 초기화 RSP = RBP
- Stack Frame의 push, pop마다 RSP를 변경
- RBP는 고정되어 있으며, RSP는 스택에 변경이 발생할 때마다 움직인다.
- RIP (Register Instructoin Pointer) : 명령어 포인터. 현재 실행 중인 메서드(Java Stack top에 위치하는 스택 프레임)를 호출한 주소를 가리킨다.
PermGen 과 Metaspace의 차이점

- Method Area는 JVM 벤더마다 다르게 구현되어 있다. 이 글에서는 Hotspot JVM의 Method Area에 대해서 다루고 있다.
- Java8 버전부터 Method Area인 PermGen이 Metaspace로 완전히 대체되었다.
- PermGen과 Metaspace의 가장 큰 차이는 메모리 할당 방식에 있다.
- Heap 영역은 JVM에 의해 관리되는 영역이며, Native 영역은 OS에 의해 관리되는 영역이다.
- PermGen의 경우 Heap 영역에 인접해 있어서, JVM에 강제되어 고정된 크기를 가졌다.
Hotspot's representation of Java classes (referred to here as class meta-data) is currently stored in a portion of the Java heap referred to as the permanent generation.
In addition, interned Strings and class static variables are stored in the permanent generation.
https://openjdk.org/jeps/122
-
- 만약 PermGen에 더 이상 할당할 공간이 없다면,
java.lang.OutOfMemoryError: PermGen space과 같이 에러가 발생했다.- Heap 영역 메모리 부족 시 발생하는
java.lang.OutOfMemoryError: Heap space space에러와 다름
- Heap 영역 메모리 부족 시 발생하는
- Metaspace는 Heap에 인접한 대신, OS에서 할당해주는 Native 영역의 메모리를 사용하며, 필요 시 OS가 동적 조정 할 수 있다.
- Metaspace는 PermGen과 달리 초기 고정 크기가 없고, OS가 허용하는 한도까지 확장이 가능하다.
- 더 이상 할당할 공간이 없다면,
OutOfMemoryError: Metaspace에러 발생
- 만약 PermGen에 더 이상 할당할 공간이 없다면,
PermGen의 메모리 구조

- PermGen : Class Meta 정보, Method Meta 정보, Static 변수 및 Interned String을 포함한 상수 정보관리
- 메모리 튜닝을 위해서 Heap, Perm 튜닝 가능 :
-XX:PermSize,-XX:MaxPermSize옵션
MetaSpace의 메모리 구조

- MetaSpace : PermGen과 동일하게 메타 데이터 저장.
- 그러나 Static Object와 Interned String의 경우 Heap 영역으로 옮겨짐
- Static Object를 Heap에서 관리하게 된 이유는 객체 단위로 GC 대상이 될 수 있도록 하기 위함임
- PermGen에서 관리되었을 때는, 클래스 로더 단위로만 GC가 가능하였음. 부분적 GC 불가능
- Static Object가 GC의 대상이 되는 경우
- 애플리케이션에서 더 이상 해당 객체에 대해 참조가 없는 경우 (도달 불가능)
- 의도적으로 참조 변수에 null을 대입한 경우
- 클래스로더가 언로드 된 경우 : 해당 Static Object를 포함하는 클래스 로더가 더 이상 사용되지 않는 경우
- 클래스 로더가 해당 클래스의 Static Object들을 참조하고 있으므로, 클래스 로더가 GC의 대상이 되기 전까지는 GC의 대상이 아니다.
- 클래스 로더가 죽을 때 클래스 로더와 연관된 모든 블록을 해제하여 클래스메타 데이터에 대한 공간을 확보
- 내장 클래스로더의 경우 일반적으로 JVM이 종료될 때까지 언로드 되지 않는다. 커스텀 클래스로더의 경우 사용자 구현에 따라 언로드 될 수 있으나 일반적인 경우는 아니다.
- 애플리케이션에서 더 이상 해당 객체에 대해 참조가 없는 경우 (도달 불가능)
- 클래스 메타데이터 사용량이 MetaSpace의 최대 크기에 도달할 때 자동으로 GC 수행
- 메모리 튜닝을 위해서 Heap 영역의 튜닝만 가능 :
-XX:MetaspaceSize,-XX:MaxMetaspaceSize
https://stackoverflow.com/questions/453023/are-static-fields-open-for-garbage-collection
https://johngrib.github.io/wiki/java8-why-permgen-removed/
https://headf1rst.github.io/TIL/jvm-static
https://jonghoonpark.com/2024/04/29/java-static-object-and-gc
Runtime Constant Pool / The String Pool / The Constant Pool
The Constant Pool
- 공식문서에서는 constant_pool table 명칭.
- 컴파일 시 생성되며, 클래스 파일의 상수 값을 저장한다.
- String 뿐만 아니라 여러 데이터 타입을 포함하며, 구조체 내에서 tag로 데이터 타입 구분
cp_info { u1 tag; u1 info[]; } - https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
Runtime Constant Pool
- 컴파일 시점에 생성된 The Constant Pool이 런타임에 클래스 로더의 판단에 의해 메모리에 올라온다.
- Runtime Constant Pool 의 저장 위치는 Method Area이다.
- Runtime Constant Pool에 들어있는 모든 references들은 symbolic으로 초기화 된다.
- 런타임에 메서드 호출 시, 스택 프레임 내부의 Constanst Pool Resolution에 의해서 참조되어 Dynamic Linking 된다.
String Pool
- 공식 문서에서는 The Pool of Strings 명칭. String Constant Pool 이라고도 함
- String Pool에 대해 설명하기 전에 먼저 String Interning의 개념에 대해 알아야 한다.
- 불변하는 1개의 String 객체를 생성하여 참조하는 것이다. 각 문자열의 단일 사본을 인턴(intern)이라고 하며, String.intern()을 통해 조회된다.
- 자바의 모든 컴파일 타임 상수 문자열은 이 메서드를 사용하여 자동으로 인턴된다.
- → String Interning의 장점은 메모리 절약 및 클래스 언로드 시 GC 부담 감소이다.
- 서로 다른 두 문자열 비교 시, String Interning을 사용하지 않으면 두 문자열의 모든 문자를 비교해야 하며 일반적으로 여러 메모리 영역에서 읽어야 하므로 시간이 오래 걸린다.
- https://en.wikipedia.org/wiki/String_interning
- 단, String Pool을 사용하는 경우는 String Literal로 만든 String에만 해당된다. new String을 사용할 경우, 새로운 String 객체를 생성하기 때문에 동일 객체를 참조하지 않는다.
- == 는 객체 주소 간 비교, .equals() 는 값 간 비교를 하게 된다. 위와 같은 이유로 String 비교 시 .equals() 를 사용한다.
- 클래스 로더가 클래스들을 로드할 때, 각 클래스에 존재하는 String Literal들이 애플리케이션 레벨의 String Pool에 저장된다.
- 서로 다른 클래스들의 String Literal들은 동일한 String Object를 참조해야 한다.
- 따라서 Stack 영역이 아닌 Heap 영역에 String Pool이 저장된다.
- Stack에서는 그저 String Pool을 참조할 뿐이다.
Before JDK 7, the pool was part of permgen space, and from JDK 7 to now, it is part of the main heap memory. Class metadata, interned Strings and class static variables will be moved from the permanent generation to either the Java heap or native memory.
https://openjdk.org/jeps/122
- String Pool 내부는 HashMap으로 구성되며 각각의 bucket에는 동일한 해시코드를 가지는 String list가 저장된다.
https://www.baeldung.com/java-string-constant-pool-heap-stack
https://www.baeldung.com/java-string-pool
JNI (Java Native Interface)
- 자바에서 C와 C++로 작성된 네이티브 메서드를 사용할 수 있도록 제공되는 자바 표준 규약이다.
- VM을 제공하는 곳마다 다른 native method interface들이 존재했던 문제로 인해 이를 통일 하는 JNI가 등장했다.
- 기본 JVM의 구현에 제약을 가하지 않는다.
네이티브 메서드 사용이 필요한 경우
- 하드웨어 자원의 제어가 필요한 경우
- 자바는 JVM 위에서 구동되기 때문에 직접적으로 하드웨어 자원의 제어가 어렵다.
- 하드웨어 자원의 제어를 위한 시스템 콜 (System Call) 은 JNI를 거쳐 네이티브 메서드를 사용해 수행된다.
- 부담이 큰 프로세스 수행 시 성능 개선이 필요한 경우
- 이미 존재하는 라이브러리를 자바로 다시 작성하는 대신 재활용 하려는 경우
동작 방식
- 먼저 라이브러리의 사용 방식은 두 가지이다.
- Static Libs : Linking 과정에서 모든 라이브러리의 바이너리들이 executable program에 포함된다. 더 이상 라이브러리가 필요하지 않지만, executable file의 크기가 커진다.
- Shared Libs : 최종적인 executable file이 라이브러리들에 대한 참조만을 가진다. (코드를 포함하지 않는다.) 따라서 executable file에서 실행 시 사용하게 될 파일들과 라이브러리들에 대해 접근할 수 있는 환경이 필요하다.
- JNI는 Shared Libs 방식을 사용한다. (이진 파일에 바이트 코드와 네이티브 코드를 섞어서 쓸 수 없기 때문에)
- 따라서 Shared Lib은 네이티브 코드를 .so .dll .dylib 파일에 독립적으로 보관하며, .class 파일(바이트 코드)에 합쳐지지 않는다.
'Java' 카테고리의 다른 글
| 1. JVM 뜯어보기 - Class Loader (0) | 2025.05.14 |
|---|---|
| 0. JVM 기초와 자바의 컴파일러 및 인터프리터에 관하여 (0) | 2025.05.14 |