Search
Duplicate

Java - Garbage Collection

글감
Java
작성자
작성 일자
2024/02/08 06:43
상태
완료
공개여부
공개
Date
생성자
작업자

Garbage Collection(GC)이란?

가비지 컬렉션
가비지 컬렉션(GC, Garbage Collection)은 자바에서의 메모리 관리 방법 중 하나로, 주기적으로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체(garbage)를 제거하는 프로세스를 말한다.
C언어나 C++에서는 프로그래머가 수동으로 메모리를 할당하고 해제해주어야 한다. 반면 Java에서는 가비지 컬렉터가 메모리 관리를 대신해주기 때문에, 메모리를 효율적으로 사용할 수 있고 개발자는 메모리 관리나 누수에 대해 신경쓰지 않고 개발에만 집중할 수 있다는 장점이 있다.
가비지 컬렉션은 Java 뿐만 아니라 Python, Go, JavaScript 등 많은 프로그래밍 언어에서 기본으로 내장되어 있다.
가비지 컬렉션의 단점
가비지 컬렉션이 자동으로 사용하지 않는 메모리를 처리해준다고 해도, 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기가 힘들다.
또한 가비지 컬렉션이 동작하는 동안 다른 프로세스들이 모두 정지하기 때문에 오버헤드가 발생한다는 문제가 있다. 이를 Stop-The-World(STW)라 부른다.
이 때문에 가비지 컬렉션이 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 된다.
어플리케이션의 사용성을 유지하면서 효율적으로 가비지 컬렉션을 실행하는 최적화 작업을 GC 튜닝이라 한다.

가비지 컬렉션 대상

가비지 컬렉션은 특정 객체가 garbage인지 아닌지 여부를 판단하기 위해, 도달성과 도달능력(Reachability)이라는 개념을 적용한다.
객체에 레퍼런스가 있다면 객체가 참조되고 있는 상태(Reachable)로 구분되고, 객체에 유효한 레퍼런스가 없다면 객체가 참조되고 있지 않은 상태(Unreachable)로 구분해 수거한다.
JVM 메모리에서 실제 객체는 Heap 영역에 생성되는데, Method Area나 Stack Area에서는 해당 Heap 영역에 있는 객체 주소를 참조만 하는 형식으로 구성된다.
Heap 영역에 생성되어 있는 객체 중 메서드가 종료되거나 특정 이벤트로 인해 해당 객체를 참조하고 있는 참조 변수가 삭제되면, 해당 객체는 어디에서도 참조하지 않고 가비지 컬렉션의 대상이 된다.

가비지 컬렉션 청소 방식

Mark and Sweep
Mark-Sweep이란 가비지 컬렉션에서 사용된 객체를 솎아내는 내부 알고리즘으로, 가비지 컬렉션이 될 대상 객체를 식별(Mark)하고 제거(Sweep)파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행한다.
Mark
Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어던 객체를 참조하고 있는지 찾아서 마킹한다.
Sweep
참조하고 있지 않은 객체(Unreachable)들을 Heap에서 제거한다.
Compact
Sweep 후 Heap 영역에 분산되어 있는 객체들을 Heap의 시작 주소로 모아 압축한다.(가비지 컬렉터 종류에 따라 하지 않는 경우도 있다)

Heap 메모리의 구조

Heap 영역
JVM의 Heap 영역은 가비지 컬렉션의 대상이 되는 공간으로, 처음 설계될 당시 아래의 2가지를 전제(Weak Geneational Hypothesis)로 설계 되었다.
1.
대부분의 객체는 금방 접근 불가능한 상태(Unreachable)로 변한다.
2.
오래된 객체에서 새로운 객체로의 참조는 아주 드물게 발생한다.
즉 객체는 대부분 일회성으로 사용되고, 메모리에 오래 남아있는 경우는 드물다는 전제로 설계되었다.
이런 특성을 이용해 보다 효율적인 메모리 관리를 위해, Heap 영역을 Young GenerationOld Generation 2가지 영역으로 설계하였다.
초기 Java에는 Permanent 영역도 있었지만, Java 8부터 Native Method Stack으로 편입되었다.
Yong Generation
새롭게 생성된 객체가 할당되는 영역으로, 대부분의 객체가 금방 Unreachable 상태가 되기 때문에 많은 객체가 Young 영역에 생성되었다가 사라진다.
더욱 효율적인 GC를 위해 Young 영역을 Eden, survivor0, survivor1의 3가지 영역으로 나눈다.
Eden : new를 통해 새로 생성된 객체가 위치하는 영역으로, GC 후 살아남은 객체는 Survivor 영역으로 보낸다.
Survivor0 / Survivor1 : 최소 1번 이상의 GC에서 살아남은 객체가 존재하는 영역으로, Survivor0과 Survivor1 중 하나는 반드시 비어있어야 한다.
Young 영역에 대한 가비지 컬렉션을 Minor GC라고 부른다.
Old Generation
Yong 영역에서 Reachable 상태를 유지하며 오랫동안 살아남은 객체가 복사되는 영역이다.
Young 영역보다 훨씬 큰 메모리 영역으로, 영역이 큰 만큼 가비지 컬렉션이 오래걸리고 적게 발생한다.
Old 영역에 대한 가비지 컬렉션을 Major GC 또는 Full GC라고 부른다.

Minor GC 과정

1.
new를 통해 새로 생성된 객체는 Yong Generation의 Eden 영역에 위치
2.
객체 생성이 반복되어 Eden 영역이 가득 차면 Minor GC가 발생
3.
Reachable 및 Unreachable 객체 탐색(Mark)
4.
살아남은 객체를 Survivor 영역으로 이동
5.
Eden 영역의 Unreachable 객체들 메모리 해제(Sweep)
6.
살아남은 모든 객체들 age 1씩 증가
7.
위 과정 반복(Surviver 영역이 번갈아가며 사용된다)

Major GC 과정

1.
객체의 age가 임계값에 도달하면 Old Generation으로 이동
2.
위 과정이 반복되어 Old Generation 메모리가 부족해지면 Major GC 발생

가비지 컬렉션 알고리즘

일반적으로 Major GC는 Minor GC보다 10배 이상의 시간이 소요된다. 이 때 Stop-The-World 문제가 발생하는데, 이를 해결하기 위한 여러 가비지 컬렉션 알고리즘들이 있다.
어플리케이션 특성에 맞춰, 적합한 가비지 컬렉션 알고리즘을 적용할 수 있다.
1.
Serial GC
서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 단순한 GC로, 싱글 스레드여서 STW 시간이 가장 길다.
Minor GC에는 Mark-Sweep을 사용하고 Major GC에는 Mark-Sweep-Compact를 사용한다.
실무에서 거의 사용되지 않는다.
2.
Parallel GC
Serial GC와 동일하지만, Young 영역의 Minor GC를 멀티 스레드로 수행한다.(Old 영역은 싱글 스레드)
Java 8의 기본 설정 GC로, Serial GC에 비해 STW 시간이 짧다.
3.
Parallel Old GC
Parallel GC를 개선한 버전으로, Old 영역 GC까지 멀티 스레드로 수행한다.
새로운 가비지 컬렉션 청소 방식인 Mark-Summary-Compact 방식을 사용한다.
4.
G1(Garbage First) GC
Heap 영역을 Young / Old 영역으로 나누는게 아닌 Region이라는 새로운 개념을 도입하여, 각 영역의 역할을 동적으로 부여하는 방식이다.
Java 9 이상 버전의 기본 설정 GC로, Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보해 GC 빈도를 줄이는 원리이다.
기존 GC처럼 메모리를 탐색하는 것이 아니라, 나눠진 영역(region) 별로 GC가 발생하며 그 중 메모리가 많이 차있는 영역을 우선적으로 GC 한다.
5.
Shenandoah GC
G1 GC가 가진 pause 이슈를 해결한 알고리즘으로, Heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요된다.
레드 햇에서 개발하여 Java 12에서 release 되었다.
6.
ZGC(Z Garbage Collector)
대량의 메모리(8MB ~ 16TB)를 낮은 지연률로 처리하기 위해 디자인된 GC이다.
G1 GC처럼 ZPage라는 영역을 사용하고, ZPage는 객체 크기에 따라 2MB의 배수로 동적으로 운영된다.
최대 힙의 크기가 증가하더라도 STW 시간이 10ms를 넘지 않는다.
Java 15에 release 되었다.