
Garbage Collection(GC)
Garbage Collection(GC)는 프로그래밍 언어에서 더 이상 사용되지 않는 메모리 공간을 자동으로 해제하는 메모리 관리 기법이다. GC는 주로 다음과 같은 작업을 수행한다.
1. 참조 추적: 어떤 객체가 여전히 사용 중인지 추적한다.. 객체가 더 이상 참조되지 않으면 사용되지 않는 것으로 간주한다.
2. 메모리 해제: 더 이상 사용되지 않는 객체의 메모리를 해제하여 다른 객체가 사용할 수 있도록 한다.
GC의 주요 목적은 메모리 누수를 방지하고, 프로그래머가 수동으로 메모리를 관리해야 하는 부담을 줄이는 것이다. GC는 Java, C#, Python 등 많은 현대적인 프로그래밍 언어에서 사용된다.
장점
- 자동 메모리 관리: 개발자가 직접 메모리 할당과 해제를 관리할 필요가 없어, 메모리 누수와 같은 문제를 줄일 수 있다.
- 개발 생산성 향상: 메모리 관리에 신경 쓰지 않아도 되므로, 개발자는 비즈니스 로직에 집중할 수 있다.
- 안정성: GC는 메모리 누수와 같은 문제를 자동으로 해결하여 애플리케이션의 안정성을 높인다.
단점
- 성능 저하: GC가 실행되는 동안 애플리케이션이 일시적으로 멈추는 ‘Stop-the-World’ 현상이 발생할 수 있다. 이는 특히 Major GC에서 두드러진다.
- 예측 불가능한 지연: GC가 언제 실행될지 예측하기 어려워, 실시간 성능이 중요한 애플리케이션에서는 문제가 될 수 있다.
- 추가적인 메모리 사용: GC는 메모리 관리에 추가적인 메모리를 사용하므로, 메모리 사용량이 증가할 수 있다.
이제 본격적으로 JVM의 Garbage Collector에 대해 알아보자.
Garbage Collector 알고리즘
JVM의 가비지 컬렉터는 주로 Mark and Sweep, Generational Garbage Collection 알고리즘을 사용해 가비지 컬렉션을 수행한다
Mark and Sweep
JVM의 가비지 컬렉션(Garbage Collection, GC)은 힙(Heap) 영역에서 더 이상 필요하지 않은 메모리를 자동으로 회수하는 프로세스이다. 가비지 컬렉션의 대상은 다음과 같은 기준으로 판별되는데,
Reachable (도달 가능): 객체가 하나 이상의 변수에 의해 참조되고 있는 상태이다. 이러한 객체는 여전히 프로그램에서 사용되고 있으므로 가비지 컬렉션의 대상이 되지 않는다.
Unreachable (도달 불가능): 객체가 더 이상 어떤 변수에도 참조되지 않는 상태이다. 이러한 객체는 프로그램에서 더 이상 사용되지 않으므로 가비지 컬렉션의 대상이 된다.
Mark and Sweep 가비지 컬렉션 알고리즘은 Mark 단계에서 Reachable (도달 가능) 객체를 추적한다.
Mark 단계
루트 객체에서 시작하여 모든 참조 가능한 객체를 탐색하고, 이들을 "마크"한다.
이 과정에서는 깊이 우선 탐색(DFS)과 같은 그래프 탐색 기법을 사용하여 객체를 추적한다.
Sweep 단계
마크되지 않은 객체들을 힙에서 제거한다.
이 단계에서는 힙 메모리를 순회하며, 마크되지 않은 객체들을 해제하여 메모리를 회수한다.
Mark and Sweep 알고리즘은 순환 참조 문제를 해결할 수 있지만, 가비지 컬렉션이 실행되는 동안 프로그램이 일시 중지되는 단점(Stop-the-World)이 있다.
또한, 메모리 단편화(Fragmentation) 문제가 발생할 수 있는데
메모리 단편화(Fragmentation)는 메모리 할당과 해제 과정에서 발생하는 문제로, 사용 가능한 메모리가 충분히 존재함에도 불구하고 큰 연속된 메모리 블록을 할당할 수 없는 상황을 말한다.
따라서 Sweep 단계에서 메모리를 회수한 후 추가적인 메모리 관리 기법인 Compact 과정을 거친다.
Generational Garbage Collection (세대별 가비지 컬렉션)
Generational Garbage Collection (세대별 가비지 컬렉션)은 JVM에서 메모리 관리를 효율적으로 하기 위해 사용되는데 이 방식이 필요한 이유는 뭘까?
객체의 생명 주기 최적화
대부분의 객체는 짧은 시간 내에 소멸하는데, 세대별 가비지 컬렉션은 이러한 특성을 활용하여, 짧은 생명 주기를 가진 객체를 빠르게 회수할 수 있도록 설계되었다.
성능 향상
Young Generation에서 주로 발생하는 Minor GC는 빠르게 수행되며, Old Generation에서 발생하는 Major GC는 상대적으로 덜 빈번하게 발생한다. 이를 통해 전체적인 가비지 컬렉션의 성능을 향상시킬 수 있다.
메모리 단편화 방지
세대별 가비지 컬렉션은 메모리 단편화를 줄이는 데 도움이 된다. Young Generation에서 객체를 빠르게 회수하고, Old Generation에서 객체를 *압축하여 메모리 공간을 효율적으로 사용할 수 있게 된다.
*메모리 단편화를 말한다
Q, Young Generation에서는 왜 객체를 압축하는 행위가 일어나지 않을까? 메모리 단편화(Fragmentation) 문제가 일어날 수 있지 않을까?
→ Young Generation에서는 객체의 생명 주기가 짧기 때문에 대부분의 객체가 빠르게 수거되므로, 메모리 단편화가 심각하게 발생하지 않으며, 압축(compaction) 과정을 수행할 필요성이 적어진다! 큰 연속된 메모리 블록을 할당할 수 없는 상황이 오기전에 대부분의 메모리는 수거된다
압축 과정은 비용이 많이 드는 작업이기 때문에, 주로 Old Generation에서 메모리 단편화를 줄이기 위해 수행된다.
따라서, Generational Garbage Collection (세대별 가비지 컬렉션)을 수행하기 위해 힙은 여러세대로 나뉘게 되는 것이다.
이제 힙이 왜 여러 세대로 나뉘는지 이해했으니, 이러한 공간들이 어떻게 상호작용하는지 살펴보자. 다음 그림들은 JVM에서 객체 할당 및 에이징 과정을 설명한다.
새로운 객체는 모두 에덴(Eden) 공간에 할당된다. 두 서바이버(Survivor) 공간은 비어 있다.
에덴 공간이 가득 차면 Minor 가비지 컬렉션이 트리거된다.
참조된 객체는 첫 번째 서바이버 공간으로 이동하고, 참조되지 않은 객체는 에덴 공간이 비워질 때 삭제된다.
다음 Minor GC에서도 동일한 과정이 에덴 공간에서 반복된다.
참조되지 않은 객체는 삭제되고, 참조된 객체는 서바이버 공간으로 이동한다.
그러나 이번에는 두 번째 서바이버 공간(S1)으로 이동한다. 또한, 이전 Minor GC에서 첫 번째 서바이버 공간(S0)에 있던 객체들은 나이가 증가하고 S1으로 이동한다.
모든 생존 객체가 S1으로 이동하면 S0와 에덴 공간이 비워지고, 이제 서바이버 공간에는 나이가 다른 객체들이 공존하게 된다. Minor GC는 수행할때 마다 각각의 서바이버 공간이 비워지는 것이 특징이다.
Minor GC 후, 나이가 일정 임계값(이 예에서는 8)에 도달한 객체는 Young Generation에서 Old Generation으로 이동하게 된다.
Minor GC가 계속 발생하면서 생존한 객체들은 Old Generation 공간으로 계속 이동한다.
결국, Old Generation의 메모리가 가득 차게 되면 Major GC가 수행된다. 이 시점에서 살아남은 객체들을 압축하여 메모리 단편화를 줄이고, 사용 가능한 연속된 메모리 블록을 확보한다.
이렇게 해서 Generational Garbage Collection (세대별 가비지 컬렉션)의 전체 과정을 다루었다.
'Java' 카테고리의 다른 글
Gradle - Task (1) | 2025.01.19 |
---|---|
자바 동시성 이슈와 얕은 복사 차이점 (0) | 2024.11.03 |
JVM 내부 구조와 동작 원리 - Runtime Data Area (1) | 2024.09.07 |
JVM 내부 구조와 동작 원리 - Class Loader, Execution Engine (3) | 2024.09.04 |
Lambda Expression 기초 (3) | 2024.09.02 |

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!