백엔드 개발자
JVM 가비지 컬렉션 GC 본문
1. 가비지 컬렉션(GC)이란?
GC의 개념
가비지 컬렉션(Garbage Collection, GC)이란 프로그램 실행 중 필요 없어진 객체를 자동으로 정리하는 메모리 관리 기법이다. Java에서는 JVM(Java Virtual Machine)이 자동으로 GC를 수행하여 개발자가 직접 메모리를 해제할 필요 없이 효율적으로 메모리를 관리할 수 있다.
GC의 역할
- 메모리 누수 방지: 사용되지 않는 객체를 자동으로 제거하여 메모리 부족을 방지한다.
- 개발 생산성 향상: 개발자가 직접 메모리 관리를 하지 않아도 된다.
- 안전성 증가: 잘못된 메모리 접근(예: 해제된 객체 접근)으로 인한 오류를 방지한다.
GC의 대상이 되는 JVM 영역
JVM의 메모리는 여러 영역으로 나뉘며, GC는 Heap 영역을 대상으로 동작한다.
Heap 영역에는 어떤 데이터가 저장될까?
Heap 영역에는 클래스의 인스턴스(객체), 배열, 그리고 동적으로 할당된 데이터가 저장된다. 이는 프로그램 실행 중 생성되어 여러 메서드나 스레드 간 공유될 수 있는 데이터들이다. JVM의 메모리는 여러 영역으로 나뉘며, GC는 Heap 영역을 대상으로 동작한다.
Car car = new Car();
car = null;
- new Car()을 통해 생성된 객체는 힙 영역에 저장됨. car는 해당 객체를 참조함.
- car이 더이상 객체를 참조하지 않게 되면 해당 객체는 참조 되는 곳이 없음. 가비지 컬렉터의 수거 대상이 됨.
2. GC 알고리즘 종류
1) Mark and Sweep (마크 앤 스윕)
- Mark 단계: 루트 객체에서 참조되는 모든 객체를 탐색하고 표시.
- 루트 객체는 GC가 참조를 따라 탐색할 때 시작하는 객체들로, 스택(Stack)에서 참조하는 지역 변수, 정적 변수(Static Fields), 실행 중인 스레드의 레지스터, JNI(Global References) 등이 포함됨.
- Sweep 단계: 참조되지 않는 객체를 해제.
- 단점: 메모리 단편화(Fragmentation) 발생 가능.
2) Mark and Compact (마크 앤 컴팩트)
- Mark and Sweep과 유사하지만, 살아남은 객체를 연속된 공간으로 이동하여 단편화를 줄임.
3) Copying GC (복사 GC)
- Young Gen에서 사용됨. Eden과 Survivor 영역을 이용해 객체를 복사하며 메모리를 정리.
- 장점: 단편화가 발생하지 않음.
- 단점: 메모리를 두 배로 사용해야 함.
4) Generational GC (세대별 GC)
- Young Gen과 Old Gen을 나누어 관리하여 GC 성능 최적화.
- 대부분의 객체는 생성 후 빠르게 소멸하므로 Young Gen에서 빠르게 GC를 수행.
- 오래 유지되는 객체만 Old Gen으로 이동하여 GC 비용을 절감.
5) Reference Counting (참조 카운팅)
- 각 객체마다 참조 횟수를 유지하며, 참조 카운트가 0이 되면 객체를 제거.
- 장점: 즉시 객체를 해제할 수 있음.
- 단점: 순환 참조(Circular Reference) 문제 발생 가능.
- 순환 참조 문제 해결 방법:
- Java의 GC는 Reference Counting을 단독으로 사용하지 않으며, Mark and Sweep과 같은 다른 기법과 조합하여 순환 참조 문제를 해결함.
- WeakReference를 사용하여 강한 참조를 줄이면 순환 참조를 피할 수 있음.
3. GC 동작 방식
- Heap 영역 (GC 대상)
- Young Generation (Young Gen)
- Eden: 새로 생성된 객체가 저장됨.
- Survivor 0(S0), Survivor 1(S1): GC에서 살아남은 객체들이 이동.
- Old Generation (Old Gen)
- Young Gen에서 일정 횟수 이상 살아남은 객체가 이동.
- 장기간 유지되는 객체들이 저장됨.
객체의 생애주기 특성
- 대부분의 객체는 단명(Short-lived) 함. (일시적인 데이터, 요청 처리 등)
- 일부 객체는 장기 유지됨. (사용자 세션, 캐싱 등)
이러한 객체의 생애주기를 기반으로 최적화하면 GC 성능을 향상시킬 수 있어.
GC 성능 최적화
- Minor GC는 빠르게 실행 가능 (Eden 영역에서 대부분 객체를 제거)
- Major GC는 가급적 줄여야 성능이 좋아짐 (Old Generation에서 GC가 오래 걸림)
- 세대별 GC 전략을 사용하면 GC의 Overhead를 줄일 수 있음
GC 실행 과정
GC는 다양한 알고리즘을 조합하여 동작한다. Java의 GC는 단순히 하나의 방식만 사용하는 것이 아니라, 여러 개의 알고리즘을 조합하여 객체의 생애주기 및 메모리 특성을 고려한 최적화된 방식으로 동작한다.
- 객체 생성 → Eden 영역에 객체가 생성됨.
- Minor GC → Eden이 가득 차면 살아남은 객체를 Survivor 영역으로 이동.
- Major GC (Full GC) → Old Generation이 가득 차면 실행되며, 모든 GC 작업을 수행.
- 객체 삭제 및 메모리 압축 → 사용되지 않는 객체를 삭제하고 단편화를 방지하기 위해 압축 수행.
- GC 알고리즘 조합 → JVM은 Mark and Sweep, Copying GC, Reference Counting 등 여러 알고리즘을 조합하여 최적의 GC 성능을 유지한다.
GC의 특징 : Stop-The-World (STW)
GC가 실행될 때, 애플리케이션의 모든 스레드를 중지(STW, Stop-The-World)하고 GC 작업을 수행한다. 최신 GC들은 STW 시간을 최소화하는 방향으로 개선되고 있다.
GC가 실행될 때, 애플리케이션의 모든 스레드를 중지(STW, Stop-The-World)하고 GC 작업을 수행한다. 이는 GC가 객체의 참조 관계를 안전하게 탐색하고 메모리를 정리하는 동안, 다른 스레드가 데이터를 변경하지 못하도록 막기 위해 필요하다.
4. GC와 객체 참조 유형
Java에서 객체의 참조 유형에 따라 GC의 동작이 달라진다. 참조의 강도에 따라 GC가 객체를 수거하는 방식이 결정된다.
1) 강한 참조 (Strong Reference)
- 일반적인 객체 참조 방식이며, GC가 절대 제거하지 않음.
- 명시적으로 null을 할당해야 GC 대상이 됨.
Object obj = new Object(); // 강한 참조
obj = null; // GC 가능
2) 약한 참조 (Weak Reference)
- GC가 감지하면 즉시 제거되는 참조 방식.
- 캐시 같은 곳에서 사용하여 메모리를 효율적으로 관리함.
WeakReference<Object> weakObj = new WeakReference<>(new Object());
3) 소프트 참조 (Soft Reference)
- 메모리가 부족할 때만 제거됨.
- 캐시처럼 가능하면 유지하되, 필요 시 제거되는 데이터에 적합함.
SoftReference<Object> softObj = new SoftReference<>(new Object());
4) 유령 참조 (Phantom Reference)
- 객체가 GC에 의해 제거되기 직전에 이벤트를 감지하는 참조 방식.
- 네이티브 리소스 해제, 대형 객체 정리 등에 활용됨.
PhantomReference<Object> phantomObj = new PhantomReference<>(new Object(), new ReferenceQueue<>());
5. JVM GC 유형
1 ) Serial GC (단일 스레드 GC, 작은 애플리케이션에 적합)
- 단일 스레드(싱글 스레드)로 동작하는 GC로, 작은 애플리케이션이나 개발 환경에 적합
- 싱글 스레드로 GC를 수행 → 하나의 CPU 코어만 사용
- Stop-The-World(STW) 시간이 길다 → GC가 실행되면 애플리케이션이 멈춤
- Young Gen과 Old Gen 모두 순차적으로 GC 수행
- 메모리 사용량이 적음 → 작은 애플리케이션에서는 유리
✅ 장점: 단순한 구조, 적은 메모리 사용, 작은 애플리케이션에 적합
❌ 단점: 싱글 스레드 방식이므로 CPU 코어가 많은 환경에서 비효율적
💡 사용 예시: GUI 애플리케이션, 소형 서버, 테스트 환경
2 ) Parallel GC (멀티 스레드 GC, Throughput 최적화)
- 멀티 스레드를 활용하여 처리량(Throughput)을 최적화하는 GC
- 여러 개의 스레드를 활용하여 GC 수행
- STW가 발생하지만, GC 속도가 빠름
- Throughput(처리량) 최적화 → GC 수행 시간을 줄여 CPU를 애플리케이션에 더 많이 사용
- Java 7~8까지 기본 GC로 사용됨
✅ 장점: Throughput이 높은 애플리케이션에 적합, CPU 코어를 활용하여 빠르게 GC 수행
❌ 단점: STW 시간이 길어 응답 시간이 중요한 애플리케이션에는 부적합
💡 사용 예시: 배치 작업, 데이터 처리 서버
3 ) CMS GC (Concurrent Mark-Sweep, 낮은 레이턴시 GC)
- GC 실행 중에도 애플리케이션이 실행될 수 있도록 병렬(Concurrent) 방식으로 동작하는 GC
- GC 실행 중에도 애플리케이션이 실행됨 (Concurrent 방식)
- STW 시간이 짧음 → 레이턴시가 중요한 애플리케이션에 적합
- Mark-Sweep 알고리즘 사용 (객체를 제거하지만, 메모리 단편화 문제 발생 가능)
- Java 9부터 기본 GC에서 제외됨 (G1 GC가 대체함)
✅ 장점: STW 시간이 짧아 레이턴시가 중요한 애플리케이션에 적합
❌ 단점: 메모리 단편화 발생 가능, CPU 사용량이 증가할 수 있음
💡 사용 예시: 실시간 시스템, 금융 서비스
4 ) G1 GC (Garbage First, Java 9 이후 기본 GC)
- Heap을 Region 단위로 나누고, GC가 효율적인 영역을 먼저 정리하는 방식
- Heap을 여러 Region으로 분할하여 GC 수행
- Stop-The-World(STW) 시간을 줄이도록 설계됨
- Mark-and-Compact 방식 사용 → 메모리 단편화 문제 해결
- Java 9 이후 기본 GC로 채택됨
✅ 장점: STW 시간이 짧고, 메모리 단편화 문제 해결
❌ 단점: CPU 사용량이 많을 수 있음
💡 사용 예시: 대규모 애플리케이션, Java 9 이상 기본 설정
5 ) ZGC (초저지연 GC, 대규모 애플리케이션용)
- Stop-The-World(STW) 시간을 1~2ms 미만으로 유지하는 초저지연 GC
- TB 단위의 대용량 Heap 지원
- STW 시간이 매우 짧음 (~1ms)
- Concurrent 방식으로 대부분의 GC 작업 수행
✅ 장점: 대규모 서비스에서 레이턴시 최소화 가능
❌ 단점: CPU 사용량이 증가할 수 있음
💡 사용 예시: 대규모 금융 서비스, 초저지연 트랜잭션 시스템
6 ) Shenandoah GC (Red Hat 주도, 초저지연 GC)
- ZGC보다 낮은 레이턴시를 목표로 한 GC, Java 17 이후 기본 제공
- STW 시간을 최소화 (10ms 이하)
- Concurrent Compaction(비동기 압축) 지원
- ZGC와 유사하지만, 구현 방식 차이 있음
✅ 장점: 낮은 레이턴시, ZGC보다 빠른 GC 수행 가능
❌ 단점: CPU 사용량이 많을 수 있음
💡 사용 예시: 초저지연 시스템, 대용량 애플리케이션
Java 버전별 기본 GC
Java 버전기본 GC특징
Java 7 이전 | Parallel GC | 다중 스레드 활용, Throughput 최적화 |
Java 8 | G1 GC | Region 기반, 낮은 STW 유지 |
Java 11 | G1 GC / ZGC | ZGC는 초저지연 GC, TB급 Heap 지원 |
Java 17 | G1 GC / ZGC / Shenandoah GC | Shenandoah는 낮은 레이턴시 제공 |
Java 21 | ZGC / Shenandoah GC | 매우 낮은 STW 제공, 대규모 애플리케이션에 적합 |