일단 쭈욱 한번 그려봤다. 자바의 신 1권, 2권을 보면서 접했던 내용들을 전체적으로 연결시켜 보았다.
위 이미지만으로는 일부 내부 구조가 추상화되어 있기 때문에 중요한 내용들부터 ~ 흥미로웠던 내용들 순서로 한번 접근해보려 한다.
일단 파생되는 개념을 살펴보기 전에 JVM 동작 구조는 숙지하는 게 좋다. 잘 모르면 뿌리가 잡히지 않은 채로 암기만 하게 된다. 이번 포스팅에서는 Deep Dive를 위해, JVM이 무엇이고 각 내부 구조가 어떤 역할을 담당하는지 알아보자.
Java 애플리케이션이 실행될 때 JVM은 Runtime Data Area이라고 불리는 메모리 공간을 운영체제로부터 할당받는다. 이 메모리 영역은 JVM 내부에서 여러 부분으로 나뉘어 관리되는데 다음과 같다.
📌 GC의 경우 Execution Engine 내부에 존재하지만, 별도로 다루기 위해 아래 이미지 상으로는 분리시켰다.
JVM의 런타임 데이터 영역 구조를 나타낸 이미지다.
각 스레드는 독립적인 PC 레지스터, Stack, Native 메서드 스택을 갖고, Heap과 Method Area을 공유한다.
실행 순서는 다음과 같은데, |
먼저 .java 파일로 소스 코드를 작성하고, javac 컴파일러가 이를 플랫폼 독립적인 바이트코드(.class)로 변환한다. |
클래스 로더가 디스크의 바이트코드를 읽어 Method Area 등에 적재한 뒤, 실행 엔진(Interpreter + JIT)을 통해 바이트코드를 해석·실행한다. 이때 자주 쓰이는 코드는 네이티브 코드로 컴파일해 성능을 최적화한다. |
런타임 동안 JVM은 Heap, Method Area, Stack, PC 레지스터, Native Method Stack 등 다양한 메모리 영역을 이용해 객체와 스레드를 관리하며, 가비지 컬렉터가 Heap의 불필요한 객체를 자동으로 회수하는 구조다. |
결론적으로 자바 애플리케이션의 메모리 관리와 디버깅 포인트를 파악하기 위해서 잘 기억해 두자.
Runtime Data Area에서 주의 깊게 보는 영역은 Heap Area와 Stack Area다.
실제 개발을 할 때 이슈로 접할 수 있는 포인트다.
Heap Memory의 경우에는 애플리케이션의 모든 부분에서 사용되며, Stack Memory의 경우에는 하나의 Thread가 실행될 때 사용된다는 점이 대표적인 차이점이다.
Heap Area
힙(Heap) 영역은 JVM 내에서 동적으로 생성되는 객체 인스턴스와 배열이 저장되는 공간이다.
즉, new 키워드로 생성하는 모든 객체와 배열 자료가 힙에 할당된다.
힙은 JVM 프로세스 전체에서 공유되는 영역으로 여러 스레드가 동시에 객체를 생성하고 사용하기 때문에 스레드 간에 공유된다. 이 때문에 동기화 이슈(concurrency issue)가 발생할 수도 있고 메모리 누수의 핵심 지점이 된다.
힙(Heap) 영역은 스택(Stack) 영역과는 다르게 메모리 호출이 끝나더라도 유지된다. 그렇기 때문에 GC 스캔 대상이다.
또한 스택은 Thread 개수마다 생성되지만 힙은 단 하나의 Heap Area만 존재한다.
Stack Area
스택(Stack) 영역은 각각의 스레드마다 독립적으로 존재하는 메모리 영역으로 메서드 실행을 위한 공간이다. 자바에서 스택은 메서드 호출 시 생성되는 스택 프레임(Stack Frame)들을 쌓아 올리는 구조로 동작한다.
각 메서드가 호출될 때마다 해당 메서드 실행에 필요한 지역 변수, 매개변수, 반환 값, 연산 중간 결과 등을 저장하는 스택 프레임이 하나 생성되어 스택에 push 된다. 메서드 실행이 끝나면 대응되는 스택 프레임이 pop 되어 제거되고, 그 안에 저장된 지역 변수 등은 함께 메모리에서 사라진다.
전형적인 LIFO (후입선출) 자료구조이며, 메서드 호출/반환에 따라 자연스럽게 메모리가 관리된다.
스택 영역에는 기본 자료형(원시 타입) 변수와 참조 타입 변수가 저장되는데 저장되는 방식이 다르다.
기본 타입 변수(ex. int, long, boolean 등)는 스택 프레임의 로컬 변수 배열에 실제 값(value) 자체로 저장된다.
반면, 참조 타입 변수는 힙 또는 메서드 영역의 실제 데이터에 대한 참조(주소) 값을 저장한다. 간단히 말하면, 객체 자체는 힙에 있고 스택에는 그 객체를 가리키는 참조값만 올라간다.
📌 Stack Frame
하나의 메서드에 필요한 메모리 덩어리를 묶어서 Stack Frame이라고 한다.
하나의 메서드당 하나의 Stack Frame이 필요하며, 메서드 호출 직전 자바 Stack에서 생성한 후 메서드를 호출하고 메서드 호출 범위가 종료되면 Stack에서 제거된다.
마지막으로 Method(Static) Area를 살펴보자.
Method Area
JVM이 읽은 클래스와 인터페이스 대한 런타임 상수 풀, 멤버 변수, 클래스 변수, 상수, 생성자, 메서드 등을 저장하는 공간이다.
Method Area에 있는 것은 어느 곳에서도 접근이 가능하다. 주요 특징으로는 프로그램 시작부터 종료될 때까지 Method Area의 데이터는 존재한다. 그렇기 때문에 static 데이터를 무분별하게 사용할 경우 메모리 부족 현상이 발생할 수 있다.
JVM 구조를 학습하면 어떤 점이 좋을까? 🤔
메모리 관련 이슈를 접했을 때 JVM 기반 지식이 있으면 문제 원인 파악까지 소요되는 리소스를 단축할 수 있기 때문에 학습한다고 생각한다. 대표적인 예시가 무엇이 있을까 찾아봤는데 다음과 같다.
StackOverflowError
StackOverflowError는 말 그대로 스택 메모리가 넘쳐서 발생하는 오류다. 일반적으로 재귀 호출이 너무 깊어지거나 서로 무한히 호출하는 순환 호출 등이 있을 때 나타난다. 스택은 메서드 호출 시 프레임이 추가되면서 사용량이 늘어나는데, 정해진 한도를 넘으면 더 이상 프레임을 할당할 수 없다.
JVM 스택 구조를 알고 있다면 문제 원인을 재귀 호출로 인한 스택 프레임 문제로 판단할 수 있다.
OutOfMemoryError
OutOfMemoryError (OOM)는 힙 메모리가 부족할 때 JVM이 발생시키는 에러다. Java 프로그램이 실행 도중 계속해서 객체를 생성하는데, 가비지 컬렉터가 사용할 수 있는 여유 공간을 만들어주지 못할 정도로 메모리 소비가 많다면 결국 힙이 한계에 도달한다.
즉, 프로그램 실행 중 생성한 모든 객체 인스턴스가 Heap에 저장되는데 JVM 메모리를 초과하면 발생하는 오류다.
해결 방법은 다음과 같다.
- 불필요하게 객체를 생성하는 코드를 수정하거나, 루프 조건을 제대로 넣어서 무한 생성을 방지한다.
- 실제 필요 이상으로 메모리를 차지하고 있는 캐시나 컬렉션을 비우거나 크기를 제한한다.
- 애플리케이션에 할당된 힙 크기를 (-Xmx 옵션 등으로) 늘려준다.
메모리 누수 (Memory Leak)
메모리 누수란 더 이상 필요하지 않은 객체가 메모리(RAM)에서 해제되지 않고 지속적으로 남아있는 현상을 말한다. 자바에서는 가비지 컬렉터가 자동으로 메모리를 관리하지만, 개발자의 실수로 인해 객체에 대한 참조를 끊지 못하면 GC는 그 객체를 영원히 유효한 것으로 여기기 때문에 회수하지 않는다.
메모리 누수를 일으키는 상황은 다음과 같다.
- 전역 컬렉션에 객체를 담고 제거하지 않는 경우
- 이벤트 리스너 또는 콜백을 등록해 놓고 해제하지 않는 경우
- 캐시에 데이터를 넣고 무한정 쌓는 경우
메모리 누수 해결을 위해서는 누수의 원인을 찾아서 참조를 적절히 해제해줘야 한다.
Heap Dump란? |
특정 시점의 힙 메모리 내부에 존재하는 모든 객체, 클래스, 참조 관계 등의 정보를 포함. |
JVM의 힙 메모리 상태를 스냅샷으로 캡쳐한 파일. |
📌 JVM은 전체적인 실행 환경
📌 스레드는 JVM 내부의 실행 단위
📌 직렬화는 힙에 있는 객체를 바이트 스트림으로 변환/복원하는 로직
📌 IO/NIO는 운영체제와의 입출력 통로 역할을 수행하는 자바 API 계층
'Dev > Java' 카테고리의 다른 글
『Java』 자바에서 Charset 클래스와 UTF-8의 관계는 무엇인가? (0) | 2024.12.10 |
---|---|
『Java』 Priority Queue (0) | 2024.12.02 |
『Java』 배열과 리스트의 차이점 (0) | 2024.11.29 |
『Java』 Map, Stack, Queue, Deque (0) | 2024.11.28 |
『Java』 Java 8, Stream 기본 (1) | 2024.11.24 |