GC(Garbage Collection)란?
자바에서 자동으로 메모리를 관리하는 기능이다.
사용하지 않는 객체들을 자동으로 메모리에서 제거하여 메모리 누수를 방지한다.
개발자가 메모리를 직접 관리할 필요가 없으며, 메모리 누수와 이중 해제 등 메모리 관련 버그를 줄여 프로그램의 안정성을 높이며, 메모리 공간을 효율적으로 사용할 수 있도록 한다.
GC의 대상
도달성(Reachability)을 통해 가비지의 대상이 되는지 판단한다.
- Reachable : 객체가 참조되고 있는 상태
- Unreachable : 객체가 참조되고 있지 않은 상태로 GC의 대상이다.
객체들은 실제로 Heap 영역에 생성되고, 스태틱(메서드) 영역과 스택 영역에서는 생성된 객체의 주소를 참조하는 형식이다.
Heap 영역 객체의 참조 변수가 삭제되는 현상이 발생하면 Unreachable가 발생한다.
이러한 객체를 가비지 컬렉션이 제거한다.
- 루트 집합체(지역변수, 활성스레드, 정적변수 등)에서 시작하여 참조를 따라갔을 때 도달할 수 없는 객
- 순환 참조가 있더라도 외부에서 이들을 참조하는 루트가 없는 경우 가비지 컬렉션이 대상이 된디.
- 메서드가 종료되거나, 해당 메서드에서 선언된 지역변수가 더 이상 사용되지 않을 때, 해당 지역 변수에 참조된 객체는 가비지이다.
- 객체가 참조되던 변수가 null로 설정되는 경우
- 익명 객체나 임시 객체의 경우 사용 범위가 제한 적이므로, 사용 후 가비지가 된다.
- 종료된 스레드의 객체
GC 동작 방식
자바는 JVM을 통해 실행되며, JVM에는 힙이라는 메모리 공간이 있다.
힙은 다음과 같이 나뉜다.
- Young Generation (새로운 객체가 생성되는 공간)
- Old Generation (오랫동안 참조되고 있는 객체들이 이동되는 공간)
- Permanent Generation (클래스 메타데이터, 컴파일된 코드를 저장하는 공간으로 자바8이후 메타 스페이스로 대체된다.)
- 객체 생성: 새로운 객체가 Young Generation의 Eden 영역에 생성된다.
- Minor GC 실행: Eden 영역이 꽉 차면 Minor GC가 실행되어, 사용 중인 객체는 Survivor 영역으로 이동되고, 나머지 객체는 삭제된다.
- Old Generation 이동: 여러 번의 Minor GC를 거친 객체는 Old Generation으로 이동한다.
- Major GC 실행:
- Old Generation이 가득 차거나, 메모리가 부족할 때, Major GC가 실행된다.
- Old Generation 내에서 사용되지 않는 객체를 제거한다.
- Full GC 실행: 전체 메모리가 부족하거나 Old Generation과 Young Generation 모두를 대상으로 메모리를 정리할 때 Full GC가 발생한다.
Garbage Collection 종류
- Minor GC
- 동작 시점 : Young Generation이 가득 찾을 때 동작한다.
- Eden 영역과 Suvivor 영역의 객체를 검사하여 더 이상 사용되지 않는 객체를 제거한다.
- Eden에서 살아남은 객체들은 Survivor 영역으로 이동하고, 일정 횟수 이상 살아남은 객체들은 Old Generation으로 이동한다.
- 비교적 빠른 처리가 가능하며, 자주 발생한다.
- Major GC (Old Generation GC, Full GC)
- 동작 시점 : Old Generation이 가득 찬 경우, Permanent/metaspace 영역에서 메모리 부족이 발생할 때 동작한다.
- Old Generation에서 참조되지 않는 객체를 제거한다.
- Minor GC 보다 시간이 오래 소요되며, 전체 애플리케이션을 잠시 멈추는 Stop-the-world 상태가 발생한다.
- Full GC: Old Generation과 Young Generation 모두를 청소하는 GC로, Minor GC보다 비용이 크고 성능에 영향을 많이 미친다.
Garbage Collection 언제??
- Eden 영역이 가득 차게 되면 Minor GC가 발생한다.
- 오랫동안 참조되는 객체들이 Old Generatiton으로 이동하고, Old Generation이 가득 참면 Major GC 또는 Full GC가 발생한다.
- 메모리 부족으로 더 이상 객체를 할당할 수 없을 때 GC가 발생한다.
- 명시적으로 System.gc()를 호출하여 GC를 실행할 수 있지만, 즉시 실행되지 않으며, JVM은 이를 무시할 수 있다.
왜 필요할까?
- 개발자가 메모리 할당 및 해제를 수동으로 관리하지 않아도 되어, 메모리 누수를 방지하고, 안정적인 메모리 사용이 가능해진다.
- 불필요한 객체를 자동으로 정리함으로써, 사용 가능한 메모리 공간을 효율적으로 관리할 수 있다.
- 메모리 누수, 잘못된 참조로 인해 프로그램이 불안정해지는 것을 방지할 수 있다.
알고리즘
- Serial GC : 단일 스레드로 가비지 컬렉션을 수행하는 가장 기본적인 알고리즘으로, 작은 애플리케이션에 적합하다.
- Parallel GC : 여러 스레드를 사용해 가비지 컬렉션을 수행하여 성능을 높이는 방식이다.
- CMS : Old Generation에서 GC를 백그라운드에서 수행하는 방식이다.
- G1 GC : 힙 메모리를 여러 구역으로 나누고, 가장 가비지가 많은 영역을 우선적으로 수집하는 방식으로, 큰 애플리케이션에 효율적이다.
단점
- Stop-the-world : 가비지 컬렉션 중 애플리케이션 실행을 멈추는 현상으로, Major CG에서 큰 영향을 미친다.
성능 최적화가 필요한 실시간 시스템에서 문제가 될 수 있다. - 가비지 컬렉션 자체도 메모리 및 CPU 리소스를 소모하는 작업으로, 성능저하가 발생할 수 있다.
- 메모리가 언제 해제되는지 예측이 힘들다.
자동으로 메모리 관리가 이루어지기 때문에 개발자는 비즈니스 로직에 집중할 수 있으며, GC가 자주 실행되기 때문에 메모리 누수나 해제 오류에 대해 걱정할 필요가 줄어든다.
하지만 GC가 실행될 때, 일시적으로 애플리케이션 성능이 저하될 수 있으며,
특히, Full GC는 애플리케이션을 중단시키는 경우가 많아 응답성이 떨어질 수 있다.
그리고 실행 시점을 예측할 수 없어, 특정 작업이 필요할 때 GC가 발생하면 성능에 영향을 미칠 수 있다.
GC가 비효율적으로 작동하는 경우, 필요한 메모리보다 더 많은 메모리를 소비한다.
GC 튜닝 목표
- 성능 최적화 :
GC의 성능을 조정하여 애플리케이션의 응답을 향상시키고, 불필요한 지연을 최소화한다. - 메모리 사용량 관리 :
메모리 소비를 최적화하여 애플리케이션이 사용 가능한 메모리 자원을 효율적으로 사용할 수 있도록 한다. - 리소스 균형 :
CPU, 메모리 등의 시스템 자원을 효율적으로 관리하여 전반적인 시스템 성능을 최적화한다.
G1GC
G1GC는 자바7에서 도입된 GC 알고리즘으로, Java9의 기본 알고리즘이다.
low Latency, High Throughput(처리량)을 목표, 대용량 힙을 효과적으로 관리하도록 설계되었다.
- 리전 기반의 메모리 관리 :
- 힙을 여러 개의 작은 리전으로 나누어 관리한다.
- 각 리전은 서로 독립적이기 때문에 메모리를 동적으로 할당하고 해제할 수 있어, 더 유연한 메모리 관리 기능이 가능하다.
- Pause Prediction Model
- 예측 가능한 응답 시간을 목표로 GC Pause Time를 제어할 수 있다.
- 개발자가 지정한 시간 내에 가비지 컬렉션이 완료되도록 최적화된 방식으로 동작한다.
- Garbage First
- G1GC는 가바지 객체가 많은 리전을 먼저 수집한다.
- 따라섯, 메모리 회수 효율을 높이고 애플리케이션 성능을 향상시킨다.
- Concurrnet Marking
- G1 GC는 객체를 Mark-and-sweep 방식으로 수집한다.
- 동시성 수집을 통해 애플리케이션이 실행 중일 때도 백그라운드에서 가비지를 수집한다.
- 이를 통해 Stop-the-world 시간이 줄어든다.
Mark-and-Sweep
1. Mark
이 단계에서는 루트 객체에서 시작하여 모든 참조 가능한 객체를 마크한다.
루트 객체는 애플리케이션에서 직접 참조되고 잇는 객체들로, 보통 다음과 같은 것이 포함된다.
- 스택에 있는 로컬 변수
- 전역 변수
- 정적 변수
DFS와 비슷한 방식으로, 루트 객체에서 시작하여 연결된 모든 객체를 탐색한다.
2. Sweep
마크되지 않은 객체를 찾아 회수한다. 즉, 참조되지 않는 객체가 메모리에서 제거되어 가비지가 된다.
이 과정에서 메모리가 정리되고, 가비지 객체가 차지하고 있던 메모리를 회수하여 재사용할 수 있게 된다.3. Compact
Sweep 후 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다.
리전 기반 구조의 장점
- 일관된 크기와 수명 관리
- 리전은 일정한 크기로 할당되며, 비슷한 수명과 용도를 갖는 객체들이 같은 리전에 관리된다.
- 따라서, 메모리 할당 및 해제가 예측이 가능하여, 특정 객체에 대한 메모리 패턴을 개발자가 쉽게 이해하고 관리할 수 있다.
- 간단한 메모리 할당과 해제
- 객체를 리전에 할당할 때 메모리 블록을 검색할 필요가 없기 때문에, 메모리 할당이 더 간단하고 빠르다.
- 특정 리전이 더 이상 필요하지 않은 경우, 전체 리전을 한 번에 해제할 수 있다.
- 동적 메모리 조정
- 메모리 사용 패턴에 따라 동적으로 리전 할당 및 해제할 수 있다.
- 특정 영역에서 메모리를 조정할 필요가 없으며, GC가 자동으로 이를 조정한다.
- 가비지 객체 수집 우선
- 가비기 객체가 많은 리전을 우선적으로 수집하여 불필요한 메모리 회수를 줄인다.
- 이로 인해 개발자는 소스 코드에 메모리 최적화 로직을 추가할 필요가 없다.
- GC 간격 예측
- G1 GC와 같은 리전 기반 GC는 GC간격을 예측할 수 있어, 성능 예측이 가능하다.
- 예측 가능한 GC 간격은 성능 튜닝을 용이하게 한다.
결과적으로, G1GC는 메모리 관리의 복잡성을 줄이고 성능을 안정적으로 유지하는 데 중점을 둔다.
이로써, 개발자는 GC의 세부 조정보다는 비즈니스 로직에 집중할 수 있다.