
https://hwlee9905.tistory.com/45
JVM 내부 구조와 동작 원리 - Class Loader, Execution Engine
JVM은 왜 필요한가? 1. 플랫폼 독립성기존의 C와 같은 언어는 운영체제 별로 개발자가 코드를 달리 작성해야 했는데, 자바 프로그램은 한 번 작성하면, JVM이 설치된 모든 플랫폼에서 실행될 수 있
hwlee9905.tistory.com
이전 글에서 이어집니다.
런타임 데이터 영역(Runtime Data Area)
런타임 데이터 영역(Runtime Data Area)은 JVM(Java Virtual Machine)이 실행 중에 사용하는 메모리 영역을 의미한다. 이 영역은 여러 부분으로 나뉘며, 각각의 부분은 JVM이 프로그램을 실행하는 동안 특정한 역할을 한다.
메서드 영역(Method Area)
메서드 영역(Method Area)은 JVM의 런타임 데이터 영역(Runtime Data Area) 중 하나로, 클래스 수준의 데이터를 저장하는 메모리 공간, JVM이 클래스를 로드할 때 메서드의 바이트코드(실행 로직), static 변수, 클래스 구조 정보 등을 저장하는 공간이다.
저장되는 정보는 다음과 같다.
저장되는 정보
✅ 메서드의 실행 코드 (바이트코드 형태)
✅ 클래스의 필드 정보 (static 변수 포함)
✅ 클래스의 상속 및 인터페이스 정보
✅ Runtime Constant Pool (Method Area 내부의 한 영역)
런타임 상수 풀 (Runtime Constant Pool)
런타임 상수 풀(Runtime Constant Pool)은 메서드 영역 내에 존재하는 데이터 구조로, 클래스나 인터페이스의 상수(Constant)와 메서드, 필드에 대한 모든 메타데이터를 저장한다.
저장되는 정보는 다음과 같다.
저장되는 정보
✅ 문자열 리터럴 ("hello" 같은 문자열)
✅ 메서드 이름 및 시그니처 (매개변수, 반환타입 등)
✅ 필드 이름 및 데이터 타입
✅ 클래스 이름 및 시그니처
✅ 인터페이스 및 상속 정보
이 두 영역은 클래스 로딩과 메모리 관리를 담당하는 역할이지만, 한눈에 보기엔 두 영역의 차이점이 무엇인지 알아보기 힘들 수 도 있다.
간단히 정리 하자면,
✔ Method Area → 실제 메서드의 바이트코드(실행 로직) 저장
✔ Runtime Constant Pool → 메서드 이름, 시그니처, 필드 참조 정보 같은 메타데이터 저장
클래스의 전체적인 구조를 생성하는 것이다. 따라서, 후술할 heap과 stack에서도 메소드 영역에 존재하지 않는 클래스를 객체로 참조할 수 없으며, 생명주기 또한 JVM 생성과 종료시까지 이어지게 된다.
상수(Constant)의 저장은 런타임 상수 풀(Runtime Constant Pool)에서 이루어지고, 런타임 상수 풀(Runtime Constant Pool)은 각 메서드 영역(Method Area)에 저장된다는 사실에 주의하자.
힙 영역 (Heap Area)
힙 영역은 JVM에서 동적으로 생성된 객체와 배열을 저장하는 메모리 공간이다. 모든 객체와 배열은 힙 영역에 할당되며, 이들은 프로그램 실행 중에 동적으로 생성되고 관리된다.
즉, 모든 객체 인스턴스는 힙 영역에 저장된다고 생각하자. 클래스의 인스턴스, 인스턴스의 필드, 배열, 컬렉션 등 모든 동적으로 생성된 데이터 구조를 포함한다.
동적 메모리 할당: 객체와 배열은 힙 영역에 동적으로 할당된다.
가비지 컬렉션: 힙 영역의 메모리는 가비지 컬렉터(Garbage Collector)에 의해 관리된다. 더 이상 참조되지 않는 객체는 가비지 컬렉션을 통해 메모리에서 해제된다.
공유 메모리: 힙 영역은 모든 스레드가 공유하는 메모리 공간이다. 따라서 멀티스레드 환경에서 동기화 문제가 발생할 수 있다.
- Young Generation: 새롭게 생성된 객체가 할당되는 영역이다. Young Generation은 다시 Eden Space와 두 개의 Survivor Space로 나뉜다.
- Eden Space: 새롭게 생성된 객체가 처음으로 할당되는 공간이다.
- Survivor Space: Eden Space에서 살아남은 객체가 이동되는 공간이다. 두 개의 Survivor Space가 있으며, 하나는 항상 비어 있다.
- Old Generation: Young Generation에서 살아남은 객체가 이동되는 영역이며, 오래된 객체가 저장된다.
- Permanent Generation (PermGen): 클래스 메타데이터와 같은 JVM 내부 구조가 저장되는 영역이다. (Java 8부터는 Metaspace로 대체되었다.)
힙 영역의 메모리는 가비지 컬렉터에 의해 자동으로 관리되며 가비지 컬렉션은 사용되지 않는 객체를 찾아 메모리를 해제하게 된다.
스택 영역 (Stack Area)
스택 영역(Stack Area)은 Java 프로그램이 실행되는 동안 메서드 호출과 관련된 정보를 저장하는 메모리 영역이다.
다음은 스택 영역에 대한 주요 특징들이다.
1. 메서드 호출 스택: 각 메서드 호출 시마다 스택 프레임(Stack Frame)이 생성되는데, 스택 프레임에는 메서드의 매개변수, 지역 변수, 반환 주소, 그리고 중간 계산 결과가 저장된다.
2. LIFO 구조: 스택 영역은 Last In, First Out (LIFO) 구조로 동작한다. 즉, 마지막에 호출된 메서드가 가장 먼저 종료됩니다.
3. 자동 메모리 관리: 메서드가 종료되면 해당 메서드의 스택 프레임이 자동으로 제거되어 메모리가 해제되고, 이는 명시적인 메모리 해제 없이도 메모리 누수를 방지한다.
4. 고정 크기: 스택 영역의 크기는 JVM 시작 시 설정되며, 일반적으로 힙 영역보다 작다. 스택 오버플로우(Stack Overflow)는 메서드 호출이 너무 깊어져 스택 영역이 초과될 때 발생한다.
5. 스레드별 스택: 각 스레드는 독립적인 스택 영역을 가진다. 따라서 여러 스레드가 동시에 실행될 때도 각 스레드의 메서드 호출 정보는 서로 간섭하지 않는다.
사실 스레드마다 스택 뿐만아니라 Native Method Stack 또한 생성 될 수 있다.
Java 애플리케이션에서 네이티브 메서드를 호출할 때, JNI (Java Native Interface)를 통해 호출이 이루어지는데, 이때 Native Method Stack이 사용되는 것이다.
JNI는 Java 애플리케이션이 네이티브 메서드를 호출할 수 있도록 하는 인터페이스인데, 네이티브 메서드가 호출되면, 해당 네이티브 메서드의 스택 프레임이 네이티브 메서드 스택에 추가되고, 네이티브 메서드가 실행되는 동안 필요한 데이터를 저장하게 된다.
JNI는 네이티브 메서드가 Java 객체 및 메서드에 접근할 수 있도록 환경을 설정하는데, 이 환경 정보또한 Native Method Stack에 저장될 수 있다.
스택 영역 (Stack Area)과 힙 영역 (Heap Area)의 관계
스택 영역(Stack Area)과 힙 영역(Heap Area)은 Java 프로그램의 메모리 관리에서 중요한 역할을 하는데, 두 영역은 서로 다른 목적과 특성을 가지고 있으며, JVM의 목적을 중심적으로 서로의 영역이 어떻게 상호작용 하고, 어떤 차이점이 있는지 알아보자.
메모리 할당
스택 영역: 메서드 호출 시마다 스택 프레임이 생성되며, 메서드의 매개변수, 지역 변수, 반환 주소 등이 저장된다. 메서드가 종료되면 해당 스택 프레임이 제거된다.
힙 영역: 동적으로 생성된 객체와 배열이 저장되고 객체는 new 키워드를 사용하여 힙에 할당된다.
생명 주기
스택 영역: 메서드 호출과 함께 생성되고, 메서드가 종료되면 자동으로 해제된다.
힙 영역: 객체가 더 이상 참조되지 않을 때까지 존재하며, 가비지 컬렉터에 의해 자동으로 해제된다.
접근 방식
스택 영역: LIFO(Last In, First Out) 방식으로 접근되고, 가장 마지막에 추가된 스택 프레임이 가장 먼저 제거된다.
힙 영역: 객체는 참조를 통해 접근되고, 여러 스레드가 동시에 접근할 수 있다.
메모리 크기
스택 영역: 크기가 상대적으로 작으며, JVM 시작 시 고정된다.
힙 영역: 크기가 상대적으로 크며, JVM 시작 시 설정할 수 있고 동적으로 확장될 수 있다.
상호 관계
스택 영역에 저장된 참조 변수는 힙 영역에 할당된 객체를 가리키게 된다. 예를 들어, 메서드의 지역 변수로 선언된 객체 참조는 힙에 할당된 실제 객체를 참조한다.
메서드 호출 시 스택 프레임에 저장된 객체 참조를 통해 힙 영역의 객체에 접근하고 조작할 수 있게 된다.
그래서 최종적인 런타임 데이터 영역(Runtime Data Area)의 그림은 이렇게 된다.
쓰레드가 많아질수록 각 쓰레드마다 별도의 stack 영역이 생성되고, 이 stack 영역에서 heap 메모리를 참조하는 경우가 많아지게된다. stack 영역은 주로 함수 호출과 지역 변수 저장에 사용되며, 상대적으로 크기가 작은 반면, heap 영역은 동적 메모리 할당을 위해 사용되며, 대규모 데이터나 객체가 저장되므로, 쓰레드가 많아지면 전체 메모리 사용의 대부분은 heap 영역에서 발생하게 된다.
PC Register는 Java Virtual Machine에서 각 스레드가 현재 실행 중인 명령어의 주소를 저장하는 레지스터인데, 각 스레드마다 독립적으로 존재하며, 현재 실행 중인 바이트코드 명령어의 주소를 저장하고 명령어가 실행될 때마다 PC Register는 다음 명령어의 주소로 이동해 명령어를 순차 실행 수행한다는 것만 간단하게 알고가면 충분하다.
정리
이쯤되면 한번에 많은 전체적인 그림이 잘 그려지지 않을 것이다... 그림을 보며 이해해보자
JVM의 시작을 잘 기억해보자.. 컴파일된 .class 파일이 클래스 로더로 부터 로딩되는 과정 부터 시작이다.
클래스 로더는 클래스 파일을 로드하여 Linking, Initializing을 통해 메서드 영역(Method Area)에 메모리를 할당하게 되고,프로그램이 실행되면서 new 키워드를 통해 객체와 배열이 동적으로 생성된다.
동적으로 생성된 객체는 힙 영역(Heap Area)에 저장되고, 메서드가 호출되면 스택 영역(Stack Area)에 스택 프레임이 생성되게 된다.
스택 프레임에는 메서드의 매개변수, 지역 변수, 반환 주소 등이 저장되게 되고,
메서드가 종료되면 해당 스택 프레임이 제거된다.
스택 영역에 저장된 참조 변수는 힙 영역에 할당된 객체를 가리킨다.
예를 들어, 메서드의 지역 변수로 선언된 객체 참조는 힙에 할당된 실제 객체를 참조하는데,
더 이상 참조되지 않는 객체는 가비지 컬렉터(Garbage Collector)가 힙 영역에서 해제하게 된다.
'Java' 카테고리의 다른 글
자바 동시성 이슈와 얕은 복사 차이점 (0) | 2024.11.03 |
---|---|
JVM 가비지 컬렉터의 내부 동작 원리 (1) | 2024.09.10 |
JVM 내부 구조와 동작 원리 - Class Loader, Execution Engine (3) | 2024.09.04 |
Lambda Expression 기초 (3) | 2024.09.02 |
좋은 객체 지향 설계를 위한 원칙 SOLID - 예제로 알아보기 (1) | 2024.01.25 |

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!