Java Garbage Collector
- 메모리 가비지 컬렉터(
Garbage Collector
,GC
)는 메모리 관리의 자동화된 과정으로, 프로그래머가 직접 메모리 할당과 해제를 관리하지 않아도 시스템이 이를 알아서 처리해주는 기능입니다. - 주로 자바(JVM), C#, 파이썬 등의 언어에서 사용됩니다.
GC의 역할
- 가비지 컬렉터의 기본적인 역할은 더 이상 사용되지 않는 객체(
Garbage
)를 탐지하여 해당 객체가 차지하는 메모리를 해제하는 것입니다. 이를 통해 메모리 누수(memory leak
)를 방지하고, 시스템 성능을 최적화하는 데 기여합니다.
GC의 작동원리
- 가비지 컬렉터는 여러 가지 알고리즘을 사용하여 메모리 회수를 관리합니다.
- 대표적인 방식에는 참조 카운팅(Reference Counting), 마크 앤 스위프(Mark and Sweep), 세대별 가비지 컬렉션(Generational Garbage Collection) 등이 있습니다.
1. 참조 카운팅(Reference Counting)
- 동작 원리: 각 객체가 몇 개의 참조(reference)를 가지고 있는지 카운팅합니다. 객체의 참조 카운트가 0이 되면 해당 객체는 더 이상 사용되지 않으므로 메모리를 해제합니다.
- 장점: 간단하고, 객체가 바로 사용되지 않으면 메모리가 해제되므로 실시간으로 메모리를 회수할 수 있습니다.
- 단점: 순환 참조(
Circular Reference
) 문제를 해결하지 못합니다. 즉, 서로가 서로를 참조하는 객체들이 있을 경우, 가비지 컬렉터는 이 객체들이Garbage
임을 인식하지 못하고memory leak
가 발생할 수 있습니다.
2. 마크 앤 스위프(Mark and Sweep)
- 동작 원리:
- 마킹 단계(Mark): 루트 객체(root object)에서 시작해, 도달 가능한 모든 객체를 탐색하고 마킹합니다. 루트 객체는 주로 스택, 전역 변수, 레지스터에 있는 객체들입니다.
- 스위프 단계(Sweep): 마킹되지 않은 객체들을
Garbage
로 판단하고, 이들의 메모리를 해제합니다.
- 장점:
Circular Reference
문제를 해결할 수 있습니다. 모든 객체가 도달 가능한지 여부에 따라 메모리를 회수합니다. - 단점: 마크 앤 스위프 방식은 메모리 해제 시 전체 힙(
heap
)을 탐색해야 하므로 성능에 영향을 미칠 수 있습니다. 또한, 메모리 단편화(memory fragmentation)가 발생할 수 있습니다.
세대별 가비지 컬렉션(Generational Garbage Collection)
- Java에서 사용하는 방식
- 대부분의 현대 가비지 컬렉터는 세대별 수집 방식을 사용합니다. 이 방법은 객체의 생존 기간에 따라 메모리를 관리하는 방식입니다.
- 세대별 GC의 기본 아이디어: 대부분의 객체는 매우 짧은 시간 동안만 사용되고, 오래 살아남는 객체는 몇 안 된다는 가정에 기반합니다.
- Young Generation: 새로 생성된 객체들이 들어가는 영역입니다. 대부분의 객체가 금방
Garbage
가 되므로, 자주 가비지 컬렉션이 수행됩니다. 주기적으로 Minor GC가 실행되며, 새로운 객체가 할당되는 기본 영역인 Eden Space는 수집되고, 살아남은 객체는 Survivor Space로 이동 - Old Generation: Young Generation에서 오래 살아남은 객체들이 이동하는 영역입니다. 이곳에서는 Major GC가 실행되며,
Garbage collection
이 덜 자주 발생합니다.
- Young Generation: 새로 생성된 객체들이 들어가는 영역입니다. 대부분의 객체가 금방
- Minor GC vs. Major GC:
- Minor GC: Young Generation에서 발생하며, 빠르고 효율적으로 메모리를 회수합니다.
- Major GC: Old Generation에서 발생하며, 상대적으로 시간이 오래 걸립니다.
- 장점: 세대별로 구분해 가비지 컬렉션을 수행함으로써 성능을 최적화할 수 있습니다. 대부분의 객체가 Young Generation에서 빠르게 제거되기 때문에 Major GC의 빈도를 줄일 수 있습니다.
Java에서 GC는?
Garbage Collection의 단계
Java의 가비지 컬렉션 과정은 일반적으로 다음과 같은 단계로 진행됩니다:
- 객체 생성 및 할당
new
키워드를 사용하여 객체를 생성합니다. 생성된 객체는 Young Generation의 Eden Space에 할당됩니다. JVM은 힙 메모리(Heap memory
)에서 객체를 할당합니다.
MyObject obj = new MyObject(); // 객체 생성
- 가비지 컬렉션 수행
- Young Generation에서 주기적으로 Minor GC가 발생하여, 사용되지 않는 객체를 수집합니다.
obj = null; // 객체에 대한 참조 해제
- 살아남은 객체는 Survivor Space로 이동하며, 일정 수의 가비지 컬렉션 후에도 살아남으면 Old Generation으로 이동하게 됩니다.
- Old Generation에서의 가비지 컬렉션
- Old Generation에서는 Major GC가 수행되며, 사용되지 않는 객체를 수집합니다. 이 과정에서는 마크 앤 스위프(Mark-and-Sweep) 또는 컴팩션(compaction) 기법이 사용될 수 있습니다.
Java에서의 가비지 컬렉션 구현체
Java에서는 여러 가지 GC 알고리즘과 구현체를 제공합니다. 가장 일반적으로 사용되는 GC 구현체는 다음과 같습니다:
- Serial Garbage Collector: 단일 스레드로 동작하며, 작은 애플리케이션에서 사용됩니다. GC가 실행되는 동안 애플리케이션이 일시 정지됩니다.
- Parallel Garbage Collector: 여러 스레드를 사용하여 가비지 수집을 수행합니다. 대규모 데이터 처리와 멀티코어 CPU에서 성능을 높이기 위해 설계되었습니다.
- Concurrent Mark-Sweep (CMS) Garbage Collector: 마크 단계를 애플리케이션과 동시에 수행하여 짧은 지연 시간과 응답성을 제공합니다.
- G1 (Garbage-First) Garbage Collector: 대규모 힙 메모리를 효율적으로 관리하기 위해 설계되었으며, 힙을 여러 개의 영역으로 나누어 가비지 수집을 수행합니다. G1은 성능과 예측 가능한 지연 시간의 균형을 맞추는 데 중점을 두고 있습니다.
Java에서의 Garbage Collector 사용 설정
Java에서는 GC를 설정하고 조정하기 위한 다양한 JVM 옵션을 제공합니다. 주요 옵션은 다음과 같습니다:
Xms
: 초기 힙 메모리 크기 설정Xmx
: 최대 힙 메모리 크기 설정XX:+UseSerialGC
: Serial Garbage Collector 사용XX:+UseParallelGC
: Parallel Garbage Collector 사용XX:+UseConcMarkSweepGC
: CMS Garbage Collector 사용XX:+UseG1GC
: G1 Garbage Collector 사용
public class GarbageCollectionExample {
public static void main(String[] args) {
// 객체 생성
MyObject obj1 = new MyObject();
MyObject obj2 = new MyObject();
// 객체 사용
obj1.display();
obj2.display();
// 객체 참조 해제
obj1 = null; // obj1 객체에 대한 참조 해제
obj2 = null; // obj2 객체에 대한 참조 해제
// 가비지 컬렉션 강제 호출
System.gc(); // JVM에 가비지 컬렉션 요청
}
}
class MyObject {
public void display() {
System.out.println("MyObject instance");
}
@Override
protected void finalize() throws Throwable {
System.out.println("Garbage collected!");
super.finalize();
}
}
- 이 코드에서
System.gc()
를 호출하여 GC를 요청할 수 있지만, 이는 강제 실행이 아니라 JVM이 알아서 판단할 수 있도록 하는 요청입니다. 또한,finalize()
메서드는 객체가 가비지로 수집될 때 호출됩니다. 하지만finalize()
는 사용하지 않는 것이 좋으며, 대신try-with-resources
와 같은 방법으로 자원 관리를 하도록 권장됩니다.
Garbage Collector의 장점과 단점
장점:
- 자동 메모리 관리: 프로그래머가 명시적으로 메모리를 해제할 필요가 없어 메모리 누수를 줄일 수 있습니다.
- 효율적인 메모리 사용: 세대별 가비지 컬렉션을 통해 짧은 생명주기의 객체를 빠르게 회수할 수 있어 메모리 효율성이 향상됩니다.
단점:
- Stop-the-World 이벤트: 가비지 컬렉션이 발생하는 동안 애플리케이션이 일시 정지되는
GC pause
발생 가합니니다. 이는 특히 실시간 시스템에서 문제를 일으킬 수 있습니다. 특히Major GC
가 오래 걸리면 성능 저하가 발생 가능합니다. - 메모리 단편화: 가비지 수집 후 메모리 블록이 단편화될 수 있으며, 이로 인해 메모리 사용이 비효율적일 수 있습니다. 이에 따라 메모리 압축 작업도 필요합니다.
결론
Java는 세대별 가비지 컬렉션(Generational Garbage Collection) 방식을 채택하여 객체의 생명 주기를 관리하며, 이를 통해 효율적인 메모리 관리와 가비지 수집을 수행합니다. 여러 가지 GC 구현체가 존재하며, 각각의 애플리케이션 요구 사항에 맞게 선택하고 조정할 수 있습니다. 이를 통해 Java는 개발자가 메모리 관리를 신경 쓰지 않고도 안정적이고 효율적인 프로그램을 작성할 수 있도록 지원합니다.