유효하지 않은 메모리(Garbage)를 자동으로 제거해주는 작업이다.
Java Application은 JVM
위에서 구동되는데, JVM
의 기능 중 하나이다.
더 이상 사용하지 않는 객체를 청소하여 메모리 공간을 확보할 수 있다.
Heap
영역에 객체들이 계속 쌓이면 OutOfMemoryException
이 발생한다.
이를 방지하기 위해 주기적으로 사용하지 않는 객체를 수집하여 제거해준다.
public class Main {
public static void main(String[] args) {
Person person = new Person("a", "곧 참조되지 않음");
person = new Person("b", "참조가 유지됨");
//GC 발생 가정 시점
}
}
//Person은 "name"과 "description"을 필드로 가지며, 생성자에서 차례대로 주입받게 됨.
이처럼 GC가 주로 동작하는 대상은 Heap
영역 내의 객체 중에서 참조되지 않은 데이터다.
위 코드에서 b
는 참조가 유지된 상태이기 때문에 GC의 대상이 아니지만, a
는 GC의 대상이 된다.
위의 경우처럼 참조하는 대상을 바꾸
거나 메서드 실행이 끝나서 Stack이 pop
되면 참조되지 않는 객체들이 생긴다.
참조되고 있는지에 대한 개념을 Reachability
라고 한다.
유효한 참조를 Reachable
, 참조되지 않으면 Unreachable
이라고 한다.
GC는 바로 Unreachable
한 객체들을 garbage라고 인식하게 된다.
위 그림처럼, Heap 영역 내부의 객체들은 Method Area
, Stack
, Native Stack
에서 참조되면 reachable
로 판정된다. (메서드 등에서 사용!)
(이렇게 reachable
로 인식되게 만들어주는 JVM Runtime Area
들을 root set
이라고 한다.)
🔥 reachable
이 참조하는 다른 객체들 역시 reachable
이 된다.
여기서 root set
에 의해 참조되고 있지 않은 객체들은 unreachable
로 판정되어, GC의 대상이 된다.
Stop the world
: 가비지 컬렉션을 수행하기 위해 JVM이 애플리케이션을 일시정지하는 것. 가비지 컬렉션이 실행되면 GC 작업을 맡은 스레드 이외의 스레드들은 모두 멈추고 GC 작업이 종료되면 재개한다. 너무 빈번한 GC 실행은 성능을 저하시킨다. (GC의 성능 개선을 위해 튜닝한다고 하면 보통 Stop the world
시간을 줄이는 작업을 말한다)Mark
: 애플리케이션이 일시 중지되면 GC가 참조된 객체와 연결된 객체들을 타고 이동하며 접근 가능한 객체를 식별하는 과정이다.Sweep
: 모든 객체 탐색이 끝나면 식별(Mark
)되지 않은 객체들을 메모리에서 해제시키는 과정이다.Young
: 새롭게 생성된 객체가 할당되는 영역이다. Young 영역에 대한 가비지 컬렉션을 Minor GC
라고 부른다. 대부분 객체가 금방 Unreachable
상태가 되기 때문에, 많은 객체가 Young
영역에 생성되었다가 사라진다.Old
: Young
영역에서 Reachable
상태를 유지하여 살아남은 객체가 복사되는 영역, Old
영역은 Young
영역보다 크게 할당되며, 크기가 큰 만큼 가비지는 적게 발생한다. 이 영역이 가득 차면 Major GC
가 발생한다.Eden
: 새로 생성된 객체가 할당되는 영역이다. Eden
영역이 꽉 차면 GC가 발생하면서 Mark
, Sweep
과정이 일어난다. 아직 사용중인 객체는 Survivor
영역으로 이동하며, Eden
영역은 비워진다.Survivor
: Eden
영역이 꽉 차게 되면, GC가 발생하면서 제거된 객체 외의 나머지 살아남은 객체는 다른 Survivor
영역으로 이동하게 된다.GC를 성공적으로 수행하는 알고리즘을 설계하기 위해서는 몇 가지 가설이 필요하다.
그 중 대표적인 것이 Weak Generational Hypothesis
이다.
이 가설은 대부분 객체는 빠르게 unreachable한 상태로 전환된다
고 본다.
또한, Heap
메모리 영역 기준으로 오래된 영역에서 최신 영역으로의 참조 방향은 적게 존재한다고 가정한다.
아래는 이 가설을 바탕으로 한 가장 기본적인 알고리즘들이다.
가장 기본적인 GC 알고리즘이다.
Mark Phase 단계
: root set
으로부터 출발하여, 참조되는 객체들에 대해서 Mark
(접근 가능한 객체 식별)한다.Sweep Phase 단계
: Mark Phase 단계 이후에 Mark
되지 않은 객체들을 추적하여 삭제한다.위와 같이 메모리를 해제하는 것을 알 수 있다.
하지만 이 알고리즘은 메모리가 Fragmentation(단편화)
되는 단점이 있다.
(메모리에서의 단편화
란 정렬되지 않은 조각으로 나뉘어져, 절대적인 크기는 충분함에도 추가적으로 메모리 할당이 불가한 상태를 말한다.)
이러한 단점을 해결하기 위해 Mark And Compact Algorithm
이 탄생했다.
이 알고리즘은 Mark And Sweep Algorithm
처럼 참조되는 객체들에 대해 Mark
하고, 참조되지 않으면 삭제한다. 삭제한 이후, 메모리를 정리하여 메모리 단편화를 해결한다!!!
많은 GC 방식들이 이 Algorithm을 바탕으로 구현된다.
GC는 일어나는 시점에 따라 Major GC
와 Minor GC
로 나눌 수 있다.
Minor GC
는 JVM의 Young
영역에서 일어나는 GC이다.
만약 Young
영역이 가득 차게 되어 더 이상 새로운 객체를 생성할 수 없을 때 Minor GC
가 일어난다.
이는 Mark
된 영역이 다음 영역으로 복사되면서 진행된다.
이 때, Mark
된 영역만 복사되기 때문에 삭제 과정은 이루어지지 않는다.
이는 Major GC
에 비해서 상대적으로 시간이 짧아서, Stop-The-World
가 이루어지지 않는다고 알려진다.
실제로는 Stop-The-World
가 이루어지며, GC 를 담당하는 쓰레드를 제외하고 모든 쓰레드가 멈추게 된다. 하지만 이 시간이 무시가 가능할 정도로 짧게 이루어지기 때문에 Stop-The-World 가 이루어지지 않는다고 간주하곤 한다.
이와 다르게 Major GC
는 Old
영역에서 이루어진다.
상당히 긴 시간 Stop-The-World
가 이루어진다.
이를 해결하기 위해 Major GC
에서는 우리가 앞에서 다룬 Mark And Compact Algorithm
을 기반으로 한 여러 GC 방식들이 선택 및 적용된다.
JVM의 메모리 영역, 그 중에서도 Heap
메모리를 정리해주는 GC의 기초에 대해 살펴봤다.
실제로 Serial GC
, Parallel GC
등 GC 방식을 직접 선택할 수 있다.
각자의 Java 프로그램에 맞는 GC 방식을 채택할 수 있는 개발자가 되자!!
https://github.com/NKLCWDT/cs/blob/main/Java/GarbageCollection.md
https://tecoble.techcourse.co.kr/post/2021-08-30-jvm-gc/