자바의 메모리 관리 방법 중 하나로 JVM 내 Heap 영역에서 동적으로 할당했던 메모리 중 필요없게 된 객체를 모아 주기적으로 제거하는 프로세스를 말합니다.
이는 C 언어와 다르게, Java에서는 가비지 컬렉터가 메모리 관리를 자동으로 수행해줍니다. 이로 인해 Java는 메모리를 효율적으로 사용할 수 있으며, 개발자는 메모리 누수, 관리하지 않아도 비지니스 로직 개발에만 집중할 수 있습니다.
가비지 컬렉션의 단점도 물론 존재합니다.
자동으로 처리해주지만, 언제 처리되는지 정확한 시점을 알 수 없어 제어가 어렵습니다. 또한, GC 작업 동안에 "stop the world"로 인해서 다른 동작은 멈춰버림으로써 발생하는 오버헤드 존재합니다.
가비지 컬렉션은 참조되는 객체를 "Reachable", 참조되지 않는 객체를 "Unreachable"로 구분하여 처리합니다.
이는 Mark와 Sweep 알고리즘을 통해서 사용되지 않는 객체를 찾아냅니다. 즉, 가비지 컬렉션이 될 대상 객체를 식별(Mark) 후 제거(Sweep)하며, 객체가 제거됨으로써 파편화된 메모리 영역을 앞에서부터 채워 압축(Compaction)하는 작업을 수행합니다.
Compaction 과정은 가비지 컬렉션 종류에 따라서 수행하지 않는 경우도 있습니다.
JVM의 Heap 영역은 동적으로 할당되는 공간으로서, 가비지 컬렉션 대상이 되는 영역입니다.
Heap 영역은 Weak Generational Hypothesis를 기반으로 설계되었습니다.
📜 Weak Generational Hypothesis
- 대부분의 객체는 단기간 동안만 생존한다.(young generation)
- 일부 객체만이 장기간 생존한다.(old generation)
따라서, 객체의 대부분은 짧은 시간 내에 참조를 잃게 되며, 오랫동안 남아있는 경우는 드물다는 가설입니다.
이러한 가설을 기반으로 JVM의 메모리를 보다 효율적으로 관리하기 위해서 객체의 생존기간에 따라 물리적으로 Heap 영역을 구분하게 되었고 크게 Young과 Old 영역으로 구분됩니다.
새롭게 생성된 객체에 메모리를 할당하는 영역입니다. 대부분 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라집니다.
Young 영역 내에서 발생한 GC를 Minor GC라고 합니다.
Young 영역에서 Reachable 상태를 유지하며 살아남은 객체가 복사되는 영역을 말합니다. Young 영역보다 공간이 넓으며, 그만큼 적은 GC가 발생합니다.
Old 영역에서 발생한 GC를 Major GC 또는 Full GC라고 합니다.
Heap 메모리 내에서 조금 더 효율적인 관리를 위해서 Young 영역 을 3가지 영역으로 나눴습니다.
Eden : new를 통해서 새로 생성된 객체 할당 공간
Survivor 0/ Survivor 1 : 최소 1번 이상 살아남은 객체의 할당 공간으로, 두 공간 중 하나는 반드시 비어있어야 합니다.
📜 Java 8 에서의 Permanent 영역
일반적으로 생성된 객체들 관련 주소값이 저장된 공간을 말합니다. 클래스 및 메서드 등에 대한 MetaData가 저장되는 영역입니다. Java 7까지는 Heap 영역 내 존재했지만, Java 8 이후부터 Native Method Stack 에 편입되었습니다.
Minor GC는 Young 영역 내에서 GC를 수행하며, Young 영역은 상대적으로 공간이 작으므로 메모리 상 객체를 찾아 제거하는데 적은 시간이 걸립니다.
처음 생성된 객체는 Eden 영역에 위치합니다.
객체가 생성되면서 Eden 영역이 Full로 차게되고 Minor GC가 수행됩니다.
Mark 동작을 통해서 reachable 객체를 탐색합니다.
Eden 영역에서 살아남은 객체는 둘 중 하나의 Survivor 영역으로 이동됩니다.
Eden 영역에서 unreachable 객체를 Sweep(메모리 해제)합니다.
살아남은 객체들은 age가 1씩 증가합니다.
📜 age란?
Survivor 영역 내 객체가 살아남은 횟수를 의미하며, Object Header에 기록됩니다. age를 기록하는 부분이 6bit로 되어 있습니다.
1~3 단계 반복한 뒤 살아남은 객체를 Survivor 영역으로 이동 시 비어있는 Survivor 영역으로 이동하게 됩니다.
이 후에 Eden 영역과 기존에 객체가 있던 Survivor 영역에 객체들을 Sweep합니다.
6번 단계를 수행하며, 이러한 과정을 반복합니다.
Old 영역 내에서 수행되는 GC를 말합니다.
Old 영역 내 객체들은 Young 영역에서 age 임계치에 도달한 객체들이 복사되어 저장된 객체들입니다.
Major GC는 Old 영역 메모리가 꽉 찬 경우 수행됩니다.
객체의 age가 임계치를 도달하게 되면 해당 객체들은 Old 영역으로 복사되며, 이는 promotion 이라고 합니다.
1번 과정이 반복되다가 Old 영역 내 메모리가 부족하게 되면 Major GC가 발생합니다.
Major GC가 발생 시 Thread가 멈추가 Mark and Sweep 작업을 수행하게 됩니다.(stop the world) 이는 CPU에 부담이 되며, 이와 같은 시간 지연을 최대한 줄이는 것이 GC 튜닝입니다.
JVM이 자동으로 메모리를 관리해주는 것은 좋지만, Major GC 수행 시마다 stop the world가 발생되고 이로 인해 애플리케이션이 중지되는 문제가 발생하게 됩니다. 이를 최적화하기 위해 다양한 방식의 GC 알고리즘이 개발되었습니다.
💡 GC 알고리즘 관련 조금씩 이해해보기