
JAVA : JVM 이란
Java의 JVM에 대해서 학습한 내용을 정리한 포스트입니다. 🐻 JVMJVM은 Java Virtual Machin의 약자입니다.자바 관련 서적을 보게 되면 가장 먼저 자바의 실행 과정을 설명해 줍니다. Windows, Mac, Linux 등 OS
doitwojae.tistory.com
이번 포스트는 JVM의 메모리 모델과 GC에 대해서 살펴보겠습니다.
🐻 JVM
이전 포스트에서 JVM에 대해서 살펴보았습니다. JVM의 전체적인 아키텍처 중 Runtime Data Area에 존재하는 Heap Area에 대해서 살펴보며 메모리를 관리해 주는 GC에 대해서 알아보겠습니다.
🐻 Runtime Data Area
Java는 JVM을 이용해 권한을 할당받아 프로그램을 호출합니다. Runtime Data Area는 JVM이 OS로부터 할당받은 메모리 영역입니다.

ClassLoader에 의해서 생성된 객체는 Heap Area에 할당되고, 클래스 정보들은 Method Area에 저장된 다는 것을 저번 포스터에서 살펴보았습니다. 그리고 메모리를 효율적으로 관리하기 위해서 JVM은 용도에 따라 그림과 같이 여러 영역으로 구분하여 나눠 관리합니다.
이중 객체들이 저장되는 Heap Area에 대해서 살펴보고, Java가 어떻게 메모리를 관리하는지 살펴보는 것이 이번 포스터의 목적입니다.
🐻 Heap Area
Heap Area는 모든 인스턴스 변수 및 배열에 대한 정보를 저장합니다. JVM 당 1개만 존재하는 공유자원으로 실제로 할당된 데이터가 존재하는 공간입니다.
JVM은 자바 프로그램에서 new 연산자를 사용하여 인스턴스가 생성되면, 해당 인스턴스 정보를 Heap Area에 저장합니다. 그리고 스택 영역의 변수가 Stack Area나 어디에서도 참조되지 않게 된다면, 가비지 컬렉션을 통해 제거됩니다.
[ Garbage Collection, GC ]
GC란 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap Area 영역에서 동적으로 할당했던 메모리 중 더 이상 참조되지 않는 메모리 객체를 모아 주기적으로 제거하는 백그라운드 프로세스를 말합니다.
🐻 Heap Memory
생성된 인스턴스의 정보를 저장하고 있는 Heap Area는 Young Generation과 Old Generation으로 이루어져 있습니다. 앞으로 Young Generation은 Young 영역, Old Generation은 Old 영역이라고 하겠습니다.

Heap Memory가 Young 영역과 Old 영역으로 관리되는 것은 Weak Generational Hypothesis 전제로 설계되었기 때문입니다. 이는 대부분의 생성된 객체는 접근 불가능한 상태(Unreachable)가 되고, 오래된 객체에서 새로운 객체로의 참조는 아주 적다는 것을 의미합니다.
이러한 특성을 이용해 JVM 개발자들은 보다 효율적인 메모리 관리를 위해, Heap Memory를 Young 영역과 Old 영역으로 설계하였습니다.
Reachable : 객체가 참조되고 있는 상태
Unreachable : 객체가 참조되고 있지 않은 상태 (GC의 대상이 됩니다)
🐻 Young 영역 (Young Generation)
Young 영역은 새로 할당된 객체가 저장되는 공간입니다. 이 영역은 3가지 공간이 존재합니다.
- Eden
- Survivor 0
- Survivor 1
새로 할당된 객체가 Young 영역에 저장되며 제한된 공간이 어느 순간 가득 차게 됩니다. 이때 GC가 동작하게 됩니다. GC는 더 이상 참조하지 않는 객체를 GC 합니다. Young 영역에서의 GC를 Minor GC라고 합니다.
Minor GC 이후에도 참조가 되고 있는 객체가 존재한다면, Survivor 0 또는 Survivor 1으로 이동하게 됩니다. 이동시키는 방법으로 Mark And Sweep 알고리즘을 통해 실행하게 됩니다.
Minor GC 과정
1. 처음 생성된 객체는 Young 영역의 일부인 Eden 영역에 위치합니다.
2. 객체가 계속 생성되어 Eden 영역이 가득 차게 됩니다.

3. Eden 영역이 가득차면, Minor GC가 발생하는데, 여기서 살아남은 객체(reachable)들은 Survivor 공간 중 하나로 이동합니다.
4. Eden 영역에서 사용되지 않은 객체 (unreachable)의 메모리를 해제합니다.(sweep)

5. 살아남은 모든 객체들은 age값이 1씩 증가합니다.

[ age 값이란 ]
Survivor 영역에서 객체가 살아남은 횟수를 의미합니다. Object Header에 기록되며 age 값이 임계값에 다다르면 Promotion(Old 영역으로 이동) 여부를 결정합니다.
가장 일반적인 age의 기본 임계값은 31입니다.
6. 또 다시 Eden 영역에 신규 객체들로 가득 차게 되면 다시 한번 minor GC가 발생하고 mark 합니다.

7. marking 한 객체들을 비어있는 Survivor 영역으로 이동하고 참조되지 않는 객체 메모리를 해제합니다. (sweep)

8. 다시 살아남은 모든 객체들은 age가 1씩 증가합니다.

여러 GC 사이클 후에도 살아남은 객체는 Old 영역 메모리 공간으로 승격(Promotion)하게 됩니다.

이미지 참조 :
🐻 Old 영역 (Old Generation == Tenured Space)
여러 번의 Minor GC 후에도 생존한 객체를 Old 영역에 저장합니다. Young 영역에서 살아남은 객체들이 Old 영역으로 Promotion 하면서 메모리가 쌓이게 됩니다. 이때 메모리가 가득 차게 되면, Old 영역에 있는 모든 인스턴스들을 검사하여 참조되지 않은 인스턴스(unreachable)를 한꺼번에 제거합니다. 이를 Major GC라고 합니다.
Young 영역에서 Minor GC가 수행되는 것과 Old 영역에서 Major GC가 수행되는 것은 GC를 수행하는 스레드를 제외한 모든 스레드가 작업을 중지시킵니다. 이것을 Stop-The-World라고 합니다.
[ Stop The World ]
JVM의 GC가 일어나면 GC를 담당하는 스레드를 제외한 모든 스레드가 일시적으로 정지합니다. 모든 스레드가 정지되기 때문에 애플리케이션을 이용하는 사용자에게 안 좋은 경험이 발생합니다. 또한, CPU에 부하를 주기 때문에 최적화하여 GC가 일어나는 횟수를 최소화해야 합니다.
Young 영역의 Minor GC는 상대적으로 작은 공간을 검사하기 때문에 0.5 ~ 1초 정도 소요되지만, Old 영역은 상대적으로 큰 공간을 가지고 있어, 이 공간에서 메모리 상의 객체를 제거하는데 많은 시간이 걸립니다.
🐻 Garbage Collection, GC
Heap Area의 데이터 구조에 대해서 살펴보았습니다. 메모리를 관리해주는 Garbage Collection이라는 것이 존재하고 이에 대해서 살펴보겠습니다.
Java의 Garbage Collection (GC라고 부르겠습니다)는 사용하지 않는 메모리에서 해제하는 것이 목적입니다. 다른 프로그래밍 언어 C의 경우는 메모리 할당과 제거를 수동으로 해줘야하지만, Java는 자동으로 GC가 처리해줍니다. 이외에도 파이썬, 자바 스크립트, Go 언어 등 많은 프로그래밍 언어에서 기본으로 내장되어 있습니다.
GC의 종류로는 앞서 살펴보았듯이 Minor GC와 Major GC가 존재합니다. 이 둘을 모두 Full GC라고도 합니다. GC는 자동으로 메모리를 관리해주는 대신 개발자가 언제 수행하는지 정확하게 알기 어려워 제어하기 힘듭니다. 또한 Stop-The-World(STW)가 발생하며 오버헤드가 발생합니다.
따라서 자바 개발진들은 끊임 없이 가비지 컬렉션 알고리즘을 발전시켜왔습니다.
🐻 가비지 컬렉션 청소 방식
GC가 객체의 Reachable과 Unreachable을 판단하고, Unreachable한 객체를 청소하는 방식은 Mark And Sweep 입니다.
Mark-Sweep이란 사용되는 객체를 골라내는 내부 알고리즘입니다. 가비지 컬렉션이 동작하는 아주 기본적인 청소 과정입니다.

이미지 참조 : 인파 블로그
GC가 될 대상을 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업을 수행합니다.
- Mark 과정 : Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹합니다.
- Sweep 과정 : 참조하고 있지 않은 객체 즉 Unreachable 객체들을 Heap에서 제거합니다.
- Compact 과정 : Sweep한 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분을 압축합니다.
[ Root Space ]
Root Space는 Stack의 로컬 변수, Method Area의 static 변수, Native Method Stack의 JNI 참조를 포함합니다. 이 영역에서 참조하는 Heap 메모리를 식별하고 청소합니다.

이미지 참조 : 두 가지 GC와 처리 영역들
🐻 Garbage Collection, GC 알고리즘 종류
Heap 메모리를 관리하는 GC가 발생하면 STW(Stop-The-World)가 발생합니다. 앞서 자바 개발자는 STW가 발생하여 애플리케이션이 중지되는 것을 최적화하기 위해서 다양한 알고리즘을 발전시켜왔습니다. 앞서 살펴본 Mark And Sweep 도 그 중하나의 방식입니다.
다양한 알고리즘을 적용한 GC의 종류에 대해서 살펴보겠습니다.
🐻 Serial GC
Serial GC는 클라이언트의 머신이 독립적인 애플리케이션 또는 CPU 성능이 낮을 때 유용하며 하나의 스레드만 사용합니다.
- 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 GC
- GC를 처리하는 스레드가 1개여서 가장 STW 시간이 길다.
- Minor GC에서는 Mark-Sweep을 사용하고, Major GC에는 Mark-Sweep-Compact 를 사용한다.
- 보통 실무에서 사용하는 경우는 없다.

java --XX:+UseSerialGC -jar application.jar
🐻 Paraller GC
시스템에 있는 CPU 코어의 수만큼 스레드를 만들어 이용하는 것을 제외하고는 Serial GC와 같습니다.
- Java 8의 디폴트 GC
- Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 스레드로 수행
- Old 영역은 싱글 스레드
- Serial GC에 비해 STW 시간 감소
java -XX:UseParallelGC -jar application.jar
# -XX:ParallerGCThreads=N : 사용할 스레드의 개수
🐻 Parallel Old GC
Young와 Old 영역에서 모든 GC가 모두 멀티 스레드를 사용하는 것을 제외하고 Paraller GC와 동일합니다.
- Paraller GC를 개선한 버전
- Young, Old 영역에서 멀티 스레드로 GC 수행
- 새로운 가비지 컬렉션 청소 방식인 Mark-Summary-Compact 방식을 이용
java -XX:+UseParallerOldGC -jar application.jar
🐻 CMS GC (Concurrent Mark Sweep)
CMS GC는 애플리케이션이 사용하는 스레드를 동시에 사용하여 STW 중지 상태를 최소화합니다.
- GC 과정이 매우 복잡하다
- GC 대상을 파악하는 과정이 복잡해 여러 단계로 수행되기 때문에 상대적으로 CPU 사용량이 높다.
- 메모리 파편화 문제
- Java 9 버전부터 deprecated, Java14 에서는 사용이 중지
🐻 G1GC (Garbage First)
CMS를 대체하는 것이 주된 목표로 나왔습니다. G1GC는 다른 GC와 다르게 Young, Old 영역이 없습니다. 힙 공간을 여러 개의 동일한 사이즈의 공간으로 분리하여 region으로 관리합니다.
- Java9+ 버전의 디폴트 GC
- 기존의 GC 알고리즘과 달리 Region이라는 개념을 새로 도입
- 전체 Heap 영역을 Region이라는 영역으로 체스 같이 분할하여 상황에 따라 Eden, Survivor, Old 을 동적할당

java -XX:+UseG1GC -jar application.jar
🐻 Shenandoah GC
- Java 12 release
- 레드 햇에서 개발한 GC
- 기존 CMS 단편화, G1의 pause 이슈 해결
- 강력한 동시성과 가벼운 GC 로직으로 최적화

🐻 ZGC (Z Garbage Collector)
- Java 15 release
- 대량의 메모리를 낮은 지연량
- G1의 Region처럼, ZPage라는 여역을 사용
- 힙 크기가 증가하더라도 STW가 절대 10ms넘지 않는다.
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar application.jar
ZGC에 대해서 자세한 내용은 블로그를 참고해주세요.