Garbage Collector : Generation GC, G1 GC, ZGC
들어가기 전에
저번 글에서 가비지 컬렉터 ( GC ) 의 개념과
GC 가 객체를 삭제하는 여러가지 알고리즘에 대해 알아보았다.
이번에는 GC 의 동작 방식과 GC 의 종류에 대해서 알아보려고 한다.
GC Remind - 1 : GC 란 무엇인가?
Garbage Collector : 더이상 참조하지 않는 객체를 제거하여 자동으로 메모리를 관리하는 프로세스
GC
덕분에 메모리 누수에 대한 걱정을 덜 수 있으며
Java, Python, JavaScript 등의 언어에 내장되어 있다. ( C, C++ 은 개발자가 직접 메모리 관리 )
하지만 GC 는 언제 작동하는지 정확히 알 수 없으며
GC 가 동작하는 동안 다른 Thread 의 동작이 멈추는 현상 ( STOP THE WORLD
) 이 발생 할 수 있다
그래서 너무 자주 실행되면, 프로그램 성능에 영향을 미칠 수 있다.
✅ 정리하자면
GC : 자동으로 참조하지 않는 객체를 제거하여 메모리를 관리하는 프로세스
장점
- 메모리 누수 관리를 직접 하지 않아도 된다.
단점
- GC 가 언제 작동하는지 정확히 알 수가 없다.
- GC 가 동작하는 동안 JVM 동작을 멈추고, 자주 사용하는 경우 성능에 영향을 미친다.
✅ 알아둬야할 내용
GC Root : 현재 프로세스에서 참고하고 있는 객체를 의미
메모리 단편화 : 사용하지 않는 메모리 영역이 연속적이지 않은 상태
GC Remind - 2 : GC 의 다양한 알고리즘
더이상 참조 하지 않는 객체들의 메모리를 회수하는 다양한 방식이 있었고, 4가지 방식에 대해 알아 보았다.
1. Mark & Sweap
동작 방식 : GC ROOT 에 도달가능한 객체 Mark
→ 나머지 객체 메모리 해제 Sweap
장점
- 순환참조 처리가능
문제점
- 메모리 단편화가 발생하고 ( 메모리 빈공간이 퍼져있음 )
- Stop-The-World 길다.
2. Mark & Compact
동작 방식 : Mark
→ Sweap
→ 메모리 단편화를 방지하여 사용 중인 메모리를 한쪽에 압축 Compact
( 단편화 방지 )
장점
- 단편화 문제 해결
문제점
- 객체 이동 시키는 비용이 크다
- GC 성능 저하가 일어난다.
3. Mark & Copy
동작 방식 : Mark
된 객체만 복사 하고 남은 메모리들 초기화
장점
- Sweap 하는 단계 생략 가능 → 성능 개선
문제점
- 메모리 낭비가 심함 ( 항상 복사하기 위한 메모리 공간 남겨두어야 한다. )
4. Reference Counting
동작 방식 : 객체 마다 참조수를 관리하고, 참조 수가 0 이되면 제거
장점
- 즉시 메모리 회수 가능
문제점
- 순환 참조 처리 불가 ( 서로 참조하면 0 이 되지 않음 )
- 참조하는 수 관리 오버헤드가 크다.
이렇게 발전해도 공통적인 문제점은 남아 있었다.
Stop-The-World 시간이 길었고
대부분의 객체는 생성 후 금방사라지는데, 모두 동일한
방식을 사용하다 보니 비효율 적이였고
위의 GC 들은 단일 스레드 기반이라 멀티 코어 자원을 활용하지 못하는 등의 단점이 있었다.
✅ 정리하자면
위의 GC 들의 공통적인 문제점으로
- Stop-The-World 시간이 오래 걸림
- 객체들의 생명주기가 대부분 짧은 것에 비해, 동일하게 GC 가 적용되니 비효율 적
- 단일 스레드 기반으로 개발 되다 보니, 멀티 코어에서 활용하기 어려움
등의 문제를 가지고 있다
Generation GC
객체의 생명주기를 기반으로 메모리를 Young Generation
, Old Generation
으로 나누어 관리하는 방식
세대별로 나누어 메모리를 관리하여, 생명 주기에 따라 다르게 관리할 수 있다.
대부분의 객체는 금방 GC 의 제거 대상이 되고 ( Unreachable ), 오랫동안 남아있는 객체는 드물게 존재한다
.( Weak Generation Hypothesis )
Young Generation
빈번하게 GC 가 일어나는 영역으로, 짧은 생명주기의 객체를 제거하는 영역
- Eden
객체가 처음 생성되면 할당되는 곳이다.
- Servivor Space ( S0, S1 )
Minor GC 에서 살아남은 객체가 이동한다.
S0,S1 영역이 있으며 번갈아가며 사용
된다. ( 한쪽은 반드시 비어있어야함 )
Old Generation
YG 에서 오래 생존하여 승격된 객체가 저장되는 영역, FULL GC 또는 Major GC 가 발생
여기서 말하는 Minor GC
, Major GC 또는 FULL GC
는
각각 YG에서 일어나는 GC, OG 에서 일어나는 GC 를 의미한다.
동작 방식 ( Minor GC )
- 객체가 Eden 영역에 생성
→ 모든 객체는 처음 생성 시 Eden 영역에서 생성
- Minor GC 가 발생
→ Eden 영역에 객체가 가득 차면 Minor GC 발생 , Live 객체를 Mark 하고 새로운 Survivor 공간으로 복사 ( Mark & Copy )
- Survivor 에서 새로운 Survivor 영역으로 객체 복사
→ 객체를 복사하여 하나의 영역만 사용하도록 설정
- Eden 영역과 사용하던 Survivor 영역 초기화
→ 사용했던 영역 초기화 및 새로운 Survivor 영역의 Age 값 1 증가
동작 방식 ( Major GC )
- YG 의 Survivor 영역으로 부터 객체를 받는다.
→ YG 의 AGE 가 특정 수를 넘으면 이동 OG 로 이동
- Old 영역에 객체가 가득 차면 Major GC 또는 FULL GC 가 발생
→ Major GC 실행 중
Stop-The-World
발생 우려가 있다.
장점
- 생명주기가 짧은 객체를 빠르게 제거 가능 ( 성능 향상 )
- Young/Old 영역 별 최적화된 알고리즘 적용 가능
단점
- Old Genration 의 Full GC 는 성능 저하를 가져올 수 있음
최근에 사용되는 GC 알고리즘
이제 최근에 사용되는 GC 알고리즘에 대해서 알아볼 생각이다.
특히 JVM 에서 사용되는 알고리즘 대해서 알아보자
G1GC
Garbage-1(First) Garbage Collector
: Heap 을 Region 으로 나누어 메모리의 역할을 동적으로 부여하는 방법을 사용 하는 GC
- Java 9 부터 JVM 기본 GC
- 높은 처리량과 낮은 지연시간을 추구
G1 이 Garbage-First 인 것 처럼, Garbage 가 많은 Region을 우선적으로 수집하여 효율적이다.
여기서 Region 이란 Heap 메모리를 1MB ~ 32 MB 의 고정된 크기로 나눈 영역 (JVM 이 자동 결정 )을 말하며
동적으로 아래의 여러가지 역할을 부여하는 방식을 사용한다.
- Eden
: 새로 생성된 객체가 할당되는 영역 - YG
- Survivor
: YG 의 Minor GC 에서 살아남은 객체가 저장되는 영역 - YG
- Old
: 오래 살아남아 승격된 객체가 저장되는 영역 - OG
- Humongous
: Region 의 50% 이상 차지하는 객체가 저장되는 영역
- Free
: 아직 아무 역할도 할당되지 않는 영역
이렇게 역할을 나누어, 필요할 때마다 Free 인 Region 에
새로운 역할을 부여하여 사용할 수 있으며 동적으로 변경 될 수 있다.
Region 을 사용하여 메모리를 독립적으로 관리함으로써, 메모리 단편화를 줄일 수 있고 효율적으로 사용이 가능하다.
Remembered Set 을 사용하여 자신을 참조하는 포인터를 기억하기 때문에
GC 참조 확인 시 전체 스캔 없이 빠르게 접근이 가능하다.
G1 GC 의 GC 타입
1. Minor GC
: YG 에서 발생하며 Mark&Copy 로 빠르게 수행됨
2. Major GC
: OG 에서 발생하며, 전체 OG 영역을 정리하던 것과 달리 OG 역할을 하는 Region 에 수행
3. Mixed GC
: YG,OG 영역 중 일부를 함께 수집
Eden ( Minor GC ) 수행 → OG 탐색 및 대상 선택 → OG 일부 Region 수집
여기서 Mixed GC 는 YG,OG 의 객체를 함께 수집하는 방식
으로,
기존의 Major GC ( Full GC ) 의 경우, OG 의 객체를 탐색하고 삭제하는데 시간이 오래 걸린다. ( STW 이 오래걸림 )
그래서 G1 GC 의 특징 중 하나인 Region을 사용하여 Garbage 비율이 높은 Region 을 선별적으로 수집하여 GC 를 수행한다.
즉, Minor GC 와 일부 Region 에 대한 Major GC 를 수행하는 것을 말한다.
JVM 튜닝 Option
-XX:MaxGCPauseMillis : GC 정지 시간 최대 설정
-XX:InitiatingHeapOccupancyPercent : Old 영역의 GC 트리거 시점 (% 기준)
위와 같은 설정으로 STW 의 최대 시간은 물론
CG 가 수행 되는 조건을 설정 할 수도 있다.
✅ G1 GC 정리하자면
정의
G1 GC 는 메모리를 Region 이라는 단위로 나누어 GC 를 수행하는 방법
특징
Heap Size 가 6 ~ 32 GB 인 경우 적합
Region
Heap 을 JVM 에 의해 고정된 크기로 나눈 영역
- EDEN, SURVIVOR, OLD, HUMONGOUS, FREE 역할을 할당받아 사용
- 동적으로 역할이 할당 됨
- Remembered Set 을 사용해 다른 Region 을 추적하여, GC 확인 비용 감소
Mixed GC
Minor GC 와 일부 Region 의 Major GC 를 수행
- STW 를 줄일 수 있음
- GC 효율 향상
장점
- 최대 정지 시간 설정 가능
- Region 단위 메모리 관리로 단편화 방지
- Multi Thread 를 이용한 병렬 처리 가능
- GC 효율성 향상 및 Full GC 최소화
단점
- GC 튜닝이 어려움
- 높은 CPU 오버헤드 ( Remembered Set 를 동시마킹 )
ZGC
ZGC ( Z Garbage Collector ) : Stop-The-World
을 밀리초 단위로 제한하는 고성능 GC
- Java 11 에서 실험적으로 도입하여, Java 15부터 사용 가능하다
- 초저 지연 ( 10ms ) 유지 , 대용량 Heap 에 적합
- 대규모 HEAP 지원 ( 최대 4TB )
- GC 작업 대부분을 애플리케이션 스레드와 동시적으로 처리
- ZPage 라는 Region 을 구분하지만 세대 구분 없이 다양한 크기를 동적으로 할당
ZGC 는 G1 과 달리 GC Stop-The-World 를 최소화 하는데 집중하며,
대부분의 GC 작업을 STW 없이 수행하는 것이 특징이다. G1 과 같이 메모리를 특정 영역으로
구분하여 사용하는데 고정적인 메모리로 나누는 것과 달리 다양한 크기의 메모리를 할당하며,
세대 구분을 하지 않는다.
ZGC 는 객체의 이동이나 수집 등을 어플리케이션과 동시에 수행 할 수 있도록Colored Pointer
를 사용한다.
64 비트 메모리 주소를 사용하여 객체 포인터에 추가적인 메타데이터를 저장하며,객체 참조 영역을
색깔로 구분하고 ZGC 의 핵심 기술이다.
ZPage
ZGC 에서 Heap 메모리를 나누는 논리적인 단위 ( small : 2MB, Medium: 32MB, Large : 동적 크기 )
- 객체의 크기에 따라 동적 할당 된다
- Small/Medium 은 여러 객체, Large 는 단일 큰 객체 ( 대형 배열 ) 에 저장
G1 GC 의 Region 과 달리 객체의 크기에 맞게 동적으로 할당한다. ZPage 의 Larage 의 경우
하나의 객체만 저장하여 단편화를 방지하는데 효과 적이다.
Colored Pointer
보통 64비트 중 최상위 4비트를 메타데이터로 사용하여 객체 상태를 관리하는 기술
메타데이터에 데이터를 저장하고, GC 작업을 지원한다.
구조
- 4 비트 메타데이터
- 42 비트 주소 : 실제 객체 위치
- 18 비트 예약 : 미래 확장용
메타데이터 구성
- Marked0/Marked1
객체가 참조 되어 있는지 두 개의 마킹 비트를 사용
- Remapped
객체가 새로운 메모리 위치로 이동 되었는지 확인
- Finalizable
객체가 finalize() 를 호출해야하는지 여부
Load Barrier
ZGC 에서 객체 참조를 읽을 때 실행되는 검사 메커니즘
어플리케이션이 객체 포인터를 읽을 때 마다 메타데이터 검사하여 GC 작업을 지원
- 객체 참조를 읽을 때 동작함으로 STW 없이 동시에 동작한다
- 참조를 읽을때 수행 작동함으로 CPU 사용량이 증가한다.
- Colored Pointer 를 사용하여 별도 구조 없이 객체 상태를 관리한다.
ZGC 동작 순서
🟡 1. Initial Mark (STW)
GC 사이클 시작 단계로, GC Root에서 직접 참조하는 객체들만 마킹 ( 잠시 STW 발생 )
이 시점에 ZPage 식별,
Colored Pointer에 Marked0, Marked1 등의 마킹 상태 비트 설정
🟢 2. Concurrent Mark
Application 스레드와 동시에 동작하면서,
루트 객체로부터 연결된 모든 객체를 따라가며 Live Object 마킹Colored Pointer
: 객체의 상태(Marked, Relocated 등)를 포인터 비트에 저장해 빠르게 확인 가능Load Barrier
: 객체 접근 시 상태를 확인하고 필요 시 GC 관련 동작을 삽입
→ 덕분에 STW 없이도 안전하게 마킹 가능
🔵 3. Concurrent Prepare for Relocation
살아있는 객체 중 이동 대상이 될 객체를 선정하고,
새 메모리 주소(ZPage 내) 할당 및 이동 계획 수립
🔴 4. Concurrent Relocate
GC가 백그라운드에서 객체를 새로운 주소로 복사 이때 객체를 참조하려 하면,
Load Barrier가 현재 포인터 상태를 확인하고 새 주소로 리디렉션 처리
애플리케이션은 객체가 이동 중이어도 안정적으로 접근 가능
Full STW 없이도 안전한 Compact 가능
🟣 5. Final Remap (STW)
일부 남아 있는 참조 주소 갱신
짧은 STW 발생 (몇 ms 수준)
이후 GC 사이클 종료
✅ ZGC 정리하자면
정의
초저 지연 및 대용량 HEAP 을 제공하는 GC
특징
- HEAP 은 최대 4TB 까지 지원 가능 하다.
- ZPage 라는 동적인 HEAP 구조를 가진다.
- Application 이 객체 참조 시 병렬적으로 GC 가 발생한다.
- Colored Pointer 를 사용하여 메타데이터를 사용하여 오버헤드가 적다.
장점
- 초저 지연 STW 를 경험 할 수 있다.
- 대용량 처리에 적합하다.
- Application Thread 와 병렬적으로 GC 가 일어난다.
- 오버헤드가 적다
- 대용량 데이터의 단편화가 적게 일어난다.
단점
- 객체 참조를 읽을 때, Load Barrier 가 작동하여 CPU 사용량이 늘어난다.
- Colored Pointer 가 64 bit JVM 에서만 지원되기 때문에 사용에 제한적이다.
- 튜닝하는데 복잡하다
글을 마치며
이번 글 까지 작성한 덕분에 GC 에 대한 개념을 잡아 갈 수 있었다.
G1 GC 와 ZGC 를 비교해 보는 것도 좋은 방법이 될 것 같다.
'Java' 카테고리의 다른 글
Garbage Collector 01 : 가비지 컬렉터와 기본적인 알고리즘 (0) | 2025.05.10 |
---|---|
Java - 자바는 왜 쓸까? 장단점 (2) | 2024.11.27 |
Java - 간단한 소개와 객체지향 (3) | 2024.11.05 |