개요
자바를 공부하다보면 가비지 컬렉터(GC)에 대해 자주 접하게 된다.
대부분 GC과정이 메모리 관리를 위해 사용된 객체를 해제하고 메모리 관리를 최적화 하기 위해서 여러 세대로 나누어 자주 접근하거나 다른 객체들에 의해 참조되는 객체들은 오래된 세대로 이동하여, 신세대에 비해 해제 빈도를 낮추는 전략을 사용한다. 정도로만 이해하고 있을 수 있는데,
요즘 'JVM 밑바닥까지 파헤치기' 라는 책을 읽으며 GC의 작동 원리는 이보다 훨씬 더 복잡하고 여러 과정을 거쳐서 발전해왔음을 확인할 수 있었다.
따라서 이번 포스팅에서는 용어와 알고리즘을 정리한후에 클래식 가비지 컬렉터의 특성과 종류를 시작으로 ZGC나 쉐넌 도어같은 최신 가비지 컬렉션 기술까지 특성 위주로 설명을 해보려고 한다.
용어 - Stop the world(STW)
단어에서 알수 있다싶이 가비지 컬렉션을 수행하기 위해 JVM이 애플리케이션의 모든 스레드를 일시 중지시키는 현상
이로 인해서 애플리케이션의 실행이 완전히 멈추게 된다.
실행이 멈추게 되는 원인
가비지 컬렉션 중에는 모든 객체의 접근성을 파악하고 메모리를 재배치 또는 해제해야 하기 때문에 애플리케이션 스레드가 동시에 실행되면 메모리 일관성 및 정확성 문제가 발생할 수 있기 때문에 정지를 하게 된다.
지속 시간의 기준
가비지 컬렉터의 종류, 힙의 크기 및 복잡성, 시스템의 하드웨어 성능에 따라 달라진다.
최소화 전략
최신의 가비지 컬렉터들은 STW의 영향을 줄이기 위해 점진적 컬렉션을 통해 STW를 짧게 유지하는 경우(G1), ZGC와 셰넌도어의 경우에는 애플리케이션 스레드가 실행 중인 동시에 수행해서 STW를 극단적으로 줄인다.
마크-컴팩트(Mark Compact) 알고리즘
마크-컴팩트 알고리즘은 메모리 내의 객체들을 '마킹' 단계와 '컴팩션' 단계를 거쳐 관리한다.
- 마킹 단계
루트 객체에서 시작하여 접근 가능한 모든 객체를 마킹하여 활성 객체(사용 중인 객체)를 식별한다. - 컴팩션 단계
마킹된 객체들을 메모리에 연속된 할당하여 메모리 내의 파편화를 줄인다.
사용된 GC: Serial GC, Parallel Compacting GC
마크-스윕(Mark Sweep) 알고리즘
마크-스윕 알고리즘은 메모리를 회수하려는 알고리즘이다.
- 마킹 단계
마크-컴팩트와 마찬가지로 활성 객체를 식별하기 위해 루트 객체에서 시작하여 접근 가능한 모든 객체를 마킹한다. - 스윕 단계
마킹되지 않은 객체들(가비지)을 메모리에서 제거하고, 그 공간을 자유 공간 목록에 추가한다.
마크- 컴패트와는 다르게 파편화가 발생할 수 있다.
사용된 GC: Concurrent Mark-Sweep (CMS) GC, Parallel Scavenge GC
클래식 가비지 컬렉터
자바 초기 버전에서 사용되던 가비지 컬렉션 방식을 지칭하며 단순하고 직관적인 알고리즘을 이용하여 메모리의 할당을 해제한다.
1. 시리얼 가비지 컬렉터 (Serial GC) - JDK 1.0
단일 스레드를 사용하여 수행하고 힙의 Young Generation에서 객체를 대상으로 마크-컴팩트(Mark-Compact) 방식을 사용한다.
특징
- 단순하고 예측 가능한 수행 구조
- 낮은 리소스 요구량
- 작은 힙 사이즈 또는 단일 처리 코어에서 효과적
단점
- 'Stop-the-World'가 발생하여 모든 애플리케이션 스레드가 가비지 컬렉션 동안 중단됨
- 큰 힙 또는 멀티 코어 환경에서 성능 저하가 발생할 수 있음
2. 병렬 가비지 컬렉터 (Parallel GC) - JDK 1.4
여러 스레드를 사용하여 Young Generation의 가비지 컬렉션을 병렬로 수행하고 멀티 프로세서 시스템에서 처리량을 최적화하는 데 유리하다.
특징
- 멀티스레드를 활용한 병렬 처리
- 처리량(throughput) 최적화에 초점을 맞춤
- 'Stop-the-World' 이벤트 발생
단점
- 여러 스레드를 사용하므로 CPU 자원 사용량이 높음
- 리얼타임 애플리케이션에서는 예측 불가능한 중단 시간으로 인해 문제가 발생할 수 있음
3. Concurrent Mark-Sweep (CMS) 가비지 컬렉터
응답 시간을 적게 가져가기 때문에 응답시간을 중시하는 애플리케이션에 적합하고 Old Generation의 메모리를 대상으로 마크-스윕 방식을 사용하여 메모리를 회수한다.
객체를 마킹하고 삭제하는 작업을 애플리케이션 스레드가 실행 중일 때 동시에 수행할 수 있다.
특징
- Old Generation에서 동시성을 지원하여 애플리케이션의 중단 시간을 줄임
- 중단 시간을 줄이기 위해 최적화됨
단점
- 파편화 문제와 복잡한 구현
- 가비지 컬렉션과 애플리케이션 스레드 사이의 자원 경쟁이 발생할 수 있음
클래식 GC 방식들은 각각의 애플리케이션 유형과 환경에 따라 선택적으로 사용되는데 예를 들어, 처리량을 중시하는 배치 처리 시스템에서는 병렬 가비지 컬렉터가, 응답 시간이 중요한 인터랙티브 애플리케이션에서는 CMS가 더 적합할 수 있다.
모던 가비지 컬렉터
멀티스레딩과 더 나은 동시성 지원을 통해 성능 저하를 최소화하는데에 초점이 맞춰져있다.
G1 Garbage Collector
G1(Garbage-First) 컬렉터는 JDK 7 에서 소개되었고, JDK 9부터는 기본 가비지 컬렉터로 설정되었다.
가장 큰 특징은 힙을 여러 영역(Region)으로 나누어서 메모리 관리를 더욱 효율적으로 만들어주며, 가비지 컬렉션 시간을 예측 가능하게 한다.
특징
- 힙을 다수의 작은 영역으로 나누어 관리.
- 가비지 컬렉션 시간을 예측 가능하게 관리.
- 기억 집합(Remembered Set)과 쓰기 장벽(Write Barrier)을 사용하여 애플리케이션과 GC의 상호작용 최소화.
장점
- 큰 힙 크기를 효율적으로 관리할 수 있음.
- 응답 시간을 우선시하는 애플리케이션에 유리.
- 컬렉션 시간을 줄여 성능 저하 최소화.
단점:
- 다른 가비지 컬렉터에 비해 설정과 관리가 복잡할 수 있음.
- 매우 낮은 지연 시간이 요구되는 경우 다른 가비지 컬렉터보다 성능이 떨어질 수 있음.
.
Z Garbage Collector (ZGC)
ZGC는 JDK 11에서 실험적 기능으로 도입되었다가, 무척 최근인 JDK 15에서 프로덕션이 준비가 되었다.
힙을 동적으로 할당 및 해제 가능한 큰 블록으로 관리하기 때문에 매우 큰 힙 크기를 효과적으로 처리할 수 있게 해준다.
특징
- 힙을 큰 블록으로 동적 관리.
- 컬러 포인터와 로드 배리어 기술 사용하여 'Stop-the-World' 지연을 극단적으로 줄임
장점
- 매우 큰 힙 크기(수십 테라바이트)를 효과적으로 관리 가능.
- 가비지 컬렉션 동안 애플리케이션 중단 시간이 거의 없음.
- 고성능 실시간 시스템에 적합.
단점
- 실험적 기능으로 시작되어 아직 널리 사용되지 않음.
- 초기 설정과 메모리 관리가 복잡할 수 있음.
Shenandoah
Shenandoah는 JDK 12에서 실험적기능으로 도입되었다가, JDK 14에서 적용이 되었다.
응답 시간을 최소화하는 것에 초점이 맞춰져있어서 G1과 유사한 여러 영역으로 나뉜 메모리 관리 방식을 사용하지만, 가비지 컬렉션과 애플리케이션 작업을 더욱 동시에 수행할 수 있도록 설계되었다.
중단 없는 가비지 컬렉션을 목표로 하며, 이 과정에서 '복사 정리'(Brooks Pointers) 기술을 사용하여 가비지 컬렉션 중에도 메모리 접근을 관리한다.
특징
- 응답 시간 최소화에 초점.
- G1과 유사한 지역화된 메모리 관리 방식.
- 동시성을 극대화하는 설계.
장점
- 가비지 컬렉션 중 애플리케이션의 지속적인 작업을 가능하게 함.
- 매우 낮은 지연 시간 제공.
- Brooks Pointers 기술을 사용하여 GC 중에도 메모리 접근 관리.
단점
- 많은 리소스를 요구할 수 있으며, 초기 설정이 복잡할 수 있음.
- 다른 가비지 컬렉터에 비해 리소스 사용량이 많을 수 있음.
마무리
사실 이 책을 읽고 이 글을 정리하는 과정에 있어서 각 가비지컬렉터에 대한 내용이 추상적이고 복잡한 느낌이 들어서 오랜 시간이 걸렸지만 위에 하이라이트로 작성해놓은 각 가비지 컬렉션들의 목적을 중심으로 이해하려 노력해보니 기술이나 상황에 따라서 필요한 부분으로 점차 진화가 되어갔음을 느낄수 있었다.
또한 G1, ZGC, Shenandoah 같은 최신 기술들이 어떻게 Stop-the-World 현상을 최소화하는지, 그리고 왜 그러한 기술적 진화가 필요했는지에 대한 이유를 최근 프로젝트에서 진행하고 있는 멀티 모듈화나 대용량 실시간 데이터 전송 기법 등의 기술적 요구사항에 의해서 발생되었구나~ 라는 것도 이해할수 있었다.
처음에는 자꾸 의문사하는 스프링 서버를 고치기 위해서 읽게 되었지만 다방면으로 시각이 넓어진것 같아서 오히려 좋다
의문사하는 서버 해결기
'Java' 카테고리의 다른 글
ArrayList/LinkedList 단순 순회시 For, Enhanced For , Iterator, ListIterator, Stream.forEach() 성능비교 (1) | 2024.08.27 |
---|---|
자바가상머신의 메모리 구조: JVM Runtime Data Area (0) | 2024.07.04 |
JVM스택메모리 구조 이해를 위한 바이트코드 예시 (0) | 2024.06.25 |
JVM에서 자바 메서드와 네이티브 메서드 실행의 차이점 (0) | 2024.06.24 |
Optional: 안정적인 Null 처리 그리고 orElse(), orElseGet(), orElseThrow() 에 대한 이해 (0) | 2023.10.24 |