
우리가 JVM를 실행하면 OS부터 RAM의 일부를 사용할 수 있도록 할당 받는다. 여기서 할당받은 메모리는 한정되어 있기 때문에 주어진 메모리를 효율적으로 사용하기 위해 더 이상 쓰지 않는 객체를 해제하여 빈 공간을 확보하는 것을 의미한다.
우리가 JVM를 실행하게 되면 주어진 RAM메모리를 역할에 맞게 적절히 나누어 사용하게 되는데 이 것을 런타임 데이터 영역이라고 한다. 그리고 아래 사진 중에 주로 힙이 GC의 대상이 된다. 자바 힙은 자바 애플리케이션이 사용할 수 있는 가장 큰 메모리로 자바의 거의 모든 객체 인스턴스와 배열이 힙에 할당된다.

힙 메모리의 구조는 Java8 이전과 이후로 달라짐 점이 있다. 먼저 Java 7 이전에는 아래 사진 처럼 Young + Old + Perm의 구역으로 나눠 졌고 모두 JVM이 실행하면서 할당 받은 메모리 내에 위치한다.

Java 8 이후에는 Young + Old (Heap) + Metaspace 로 Perm영역이 Metaspace로 대체 되었다. 그리고 Metaspace는 JVM이 실행하면서 할당 받은 메모리 밖 에 Native Memory로 분리되었다.
새롭게 생성된 객체가 할당(Allocation)되는 영역이다. 대부분의 객체는 생성된 후 금방 참조가 끊기는 Unreachable 상태가 되기 때문에 수많은 객체가 이 영역에서 생성되었다가 사라진다. 이 영역에서 발생하는 가비지 컬렉션을 Minor GC라고 부른다. 내부적으로는 Eden, Survivor 0, Survivor 1의 세 가지 영역으로 다시 나뉘어 효율적으로 관리된다.
Young 영역에서의 Minor GC로부터 끝까지 살아남은(Reachable 상태를 유지한) 객체들이 복사되어 이동하는 영역이다. Young 영역보다 크게 할당되며, 객체들이 오랫동안 살아남아 이동해오기 때문에 가비지는 상대적으로 적게 발생한다. 이 영역이 가득 차면 가비지 컬렉션이 발생하며, 이를 Major GC 또는 Full GC라고 부른다.
JVM이 실행되면서 로드된 클래스와 메서드의 메타데이터, static 변수, 상수 풀(Constant Pool) 등이 저장되던 영역이다. 힙(Heap) 메모리의 일부분으로 존재하며 크기가 고정되어 있어 클래스 로딩이 많아지면 OutOfMemoryError: PermGen space가 발생하는 주요 원인이 되었다.
Java 8 버전부터 기존의 Perm 영역이 삭제되고 이를 대체하기 위해 등장한 영역이다. 가장 큰 특징은 힙(Heap)이 아닌 네이티브 메모리(Native Memory) 영역을 사용한다는 점이다.
Perm에서 Heap으로 이사 온 Static 객체와 문자열 상수들도 일반 객체와 동일한 생애주기를 가진다. 처음엔 Eden에서 태어나지만 오랫동안 살아남기 때문에 대부분 Old 영역에서 머물며 GC의 관리를 받게 된다.
JVM은 효율적인 메모리 관리를 위해 Young 영역과 Old 영역에서 발생하는 가비지 컬렉션을 구분하여 수행한다.
대상: Young 영역 (Eden, Survivor 0, Survivor 1)
발생 시점: 새로운 객체가 생성되는 공간인 Eden 영역이 꽉 찼을 때 발생한다.
Java에서는 메서드 안에서 생성된 객체가 메서드 실행 종료와 함께 운명을 다하더라도 메모리에서 즉각 사라지지는 않는다. 대신 해당 객체는 참조가 끊긴 상태(Unreachable) 로 Eden 영역에 얌전히 머물렀다가 Eden 영역이 꽉 찼을 때 Minor GC가 실행된다.
대부분의 객체는 생성 직후 곧바로 Unreachable 상태가 되기 때문에 매우 빠른 속도로 처리되고 GC 수행 시 애플리케이션이 잠시 멈추는 Stop-the-world 시간이 매우 짧아 시스템 성능에 큰 영향을 주지 않는다.
살아남은 객체는 Survivor 영역으로 이동하며 이 과정이 반복되면서 성숙한 객체는 Old 영역으로 승격된다.
대상: Old 영역 (경우에 따라 Heap 전체 및 Metaspace 포함)
발생 시점: Young 영역에서 살아남은 객체들이 계속 넘어오다가 Old 영역의 메모리가 부족해질 때 발생한다.
Old 영역은 Young 영역보다 크기가 크며 오랫동안 참조되는 객체들이 모여 있어 가비지를 선별하는 데 시간이 오래 걸린다.
Stop-the-world 시간이 Minor GC에 비해 현저히 길며 이는 서비스 지연의 주요 원인이 된다.
따라서 효율적인 Java 애플리케이션 운영을 위해서는 Major GC가 발생하는 빈도를 줄이는 것이 성능 최적화가 핵심이다.
객체의 참조 여부(Reachable/Unreachable)를 정확히 판별하기 위해서는 메모리의 상태가 고정되어야 한다. 따라서 JVM은 GC를 실행할 때 GC 스레드를 제외한 모든 작업을 중단시키는데 이를 Stop-the-world라고 부른다. 어떤 GC 알고리즘을 사용하든 이 시간을 얼마나 단축하느냐가 성능의 관건이 된다.
👉 GC 알고리즘 종류
- Serial GC
- Parallel GC
- CMS(Concurrent Mark Sweep) GC
- G1(Garbage First) GC
- ZGC