2024년 10월 6일
카테고리 : JAVA이론
조회 : 326|6분 읽기
JAVA - GC
자바의 메모리 관리(GC) 방식 이해하기
자바는 객체 지향 프로그래밍 언어로 개발자들이 객체를 생성하고 조작하는 데 집중할 수 있도록 메모리 관리의 많은 부분을 자동화합니다.
이 자동 메모리 관리는 주로 **가비지 컬렉션(Garbage Collection, GC)**을 통해 이루어집니다.
같은 GC를 가지고 있는 언어의 대표적인 예는 C#, Python, JS, GO 등이 있습니다.
자바 메모리 구조
자바 애플리케이션이 실행될 때, JVM(Java Virtual Machine)은 다양한 메모리 영역을 할당하여 객체를 저장하고 관리합니다. 주요 메모리 영역은 다음과 같습니다
- 힙(Heap): 모든 객체가 저장되는 메모리 영역입니다. 힙은 다시 Young Generation과 Old Generation으로 나뉩니다.
- 메소드 영역(Method Area): 클래스 정보, 메소드, 정적 변수 등이 저장되는 영역입니다.
- 스택(Stack): 각 스레드마다 독립적으로 할당되며 메소드 호출과 로컬 변수가 저장됩니다.
- 프로그램 카운터(Program Counter Register): 각 스레드가 현재 실행 중인 명령의 주소를 가리킵니다.
- 네이티브 메모리(Native Memory): JVM 외부에서 사용하는 메모리로 네이티브 라이브러리나 JNI(Java Native Interface)를 통해 사용됩니다.
힙(Heap)의 구조:
-
Young Generation (영 세대): • Eden Space: 새로 생성된 객체가 처음 할당되는 공간입니다. 대부분의 객체가 여기에 저장됩니다. • Survivor Space: Eden Space에서 살아남은 객체가 이동하는 영역입니다. 두 개의 Survivor Space(S0, S1)가 번갈아 사용됩니다. • Young Generation에서 주기적으로 Minor GC가 발생하여 살아남은 객체는 Survivor Space로 이동하고 그렇지 않은 객체는 메모리에서 해제됩니다.
-
Old Generation (오래된 세대, Tenured Generation): • Young Generation에서 오랫동안 살아남은 객체가 이동하는 공간입니다. • Old Generation은 객체의 수명이 길어지는 경우에 사용됩니다. 이 영역에서 가비지 컬렉션이 발생하면 Major GC 또는 Full GC가 일어나는데 이는 Young Generation보다 빈도가 적고 성능에 더 큰 영향을 미칩니다.
Young Generation에서 빈번한 Minor GC가 발생하고, Old Generation에서는 더 적은 빈도로 Major GC가 발생하여 메모리를 정리합니다.
따라서 힙 메모리 내의 Young Generation과 Old Generation의 구분은 JVM 메모리 관리의 핵심이며 이를 통해 객체의 수명 주기에 따라 효율적으로 메모리를 관리할 수 있게 됩니다.
가비지 컬렉션(GC) 개요
가비지 컬렉션은 더 이상 참조되지 않는 객체를 자동으로 메모리에서 해제하여 메모리 누수를 방지하고 메모리 사용을 최적화하는 자바의 메커니즘입니다. 개발자는 객체를 생성하고 참조를 관리하는 데 집중할 수 있으며 GC가 필요하지 않은 객체를 찾아 자동으로 정리합니다.
🛠 GC의 주요 역할
- 메모리 회수: 사용되지 않는 객체의 메모리를 회수하여 재사용할 수 있게 합니다.
- 메모리 누수 방지: 불필요한 객체가 메모리에 남아 있는 것을 방지합니다.
- 애플리케이션 성능 향상: 메모리 관리의 부담을 줄여 애플리케이션의 성능을 최적화합니다.
GC의 동작 원리
GC는 주로 다음과 같은 단계로 동작합니다:
- 마크(Mark): 도달 가능한 객체를 식별하여 표시합니다.
- 스위프(Sweep): 마크되지 않은 객체를 메모리에서 해제합니다.
- 컴팩트(Compact): 메모리 단편화를 줄이기 위해 객체를 재배치합니다. (일부 GC 알고리즘에서만 사용)
🔍 도달 가능성 판단
도달 가능성은 객체가 여전히 참조되고 있는지 여부에 따라 결정됩니다. 객체가 더 이상 참조되지 않으면 그 객체는 가비지 컬렉션의 대상이 됩니다. 참조의 유형에는 강한 참조(Strong Reference), 약한 참조(Weak Reference), 순환 참조 등이 있습니다.
객체 도달 가능성의 정의
**도달 가능성(Reachability)**은 객체가 프로그램 실행 중에서 여전히 참조되고 있는지 여부를 의미합니다. 자바의 가비지 컬렉터는 특정 객체에 대해 더 이상 참조가 존재하지 않으면 그 객체를 가비지로 간주하고 메모리에서 회수합니다.
참조 유형(Reference Types)
-
강한 참조(Strong Reference):
- 가장 일반적인 참조 유형입니다.
- 객체가 강하게 참조되고 있으면 가비지 컬렉터가 절대로 그 객체를 회수하지 않습니다.
- 예시:
java
1MyObject obj = new MyObject(); // 강한 참조 - obj가 참조를 유지하는 한 이 객체는 절대 GC의 대상이 되지 않습니다.
-
약한 참조(Weak Reference):
- 객체가 약하게 참조되고 있으면 GC는 그 객체를 언제든지 회수할 수 있습니다.
- 약한 참조는 주로 캐시와 같은 메모리 민감한 데이터 구조에서 사용됩니다.
- 예시:
java
1WeakReference<MyObject> weakRef = new WeakReference<>(new MyObject()); - weakRef는 약한 참조이므로, 강한 참조가 없는 상태에서는 GC가 이 객체를 수집할 수 있습니다.
-
소프트 참조(Soft Reference):
- 약한 참조와 유사하지만, 메모리가 부족할 때만 GC에 의해 회수됩니다.
- 소프트 참조는 캐시와 같은 상황에서 사용됩니다. 메모리 부족 시에만 회수되므로 비교적 더 오래 살아남을 수 있습니다.
- 예시:
java
1SoftReference<MyObject> softRef = new SoftReference<>(new MyObject());
-
팬텀 참조(Phantom Reference):
- 객체가 소멸된 후에도 특정 작업(예: 리소스 정리)을 수행해야 할 때 사용됩니다.
- 팬텀 참조 객체는 GC가 객체를 회수한 후에야 작동되며, 팬텀 참조는 해당 객체의 직접 접근이 불가능합니다.
- 예시:
java
1PhantomReference<MyObject> phantomRef = new PhantomReference<>(new MyObject(), referenceQueue);
순환 참조(Circular Reference)
순환 참조는 객체들이 서로를 참조하는 경우를 말합니다. 순환 참조가 있어도 외부에서 더 이상 해당 객체들을 참조하지 않으면 GC는 이 객체들을 회수할 수 있습니다.
- 예시: A 객체가 B 객체를 참조하고, B 객체가 다시 A 객체를 참조하는 구조일 때, A와 B 모두 다른 외부에서 참조되지 않는다면 GC는 이를 순환 참조라고 간주하고 수집할 수 있습니다.
GC의 도달성 판별 과정
가비지 컬렉션이 작동할 때, JVM은 **GC 루트(GC Roots)**라고 불리는 특별한 참조 집합에서 출발하여 객체 그래프를 탐색합니다. 이 과정에서 GC는 도달 가능한 객체는 계속 유지하고, 도달할 수 없는 객체는 가비지로 간주하여 메모리에서 제거합니다.
GC 루트로 간주되는 것들:
- 스택의 로컬 변수: 현재 실행 중인 메소드의 변수들.
- 정적 필드: 클래스의 정적 변수들.
- 활성 스레드: 현재 실행 중인 스레드.
전체적인 요약:
- 도달 가능성: GC는 참조되지 않는 객체(더 이상 도달할 수 없는 객체)를 수집합니다.
- 참조 유형:
- 강한 참조: GC가 절대 회수하지 않습니다.
- 약한 참조: GC는 이 객체를 언제든지 회수할 수 있습니다.
- 소프트 참조: 메모리가 부족할 때만 회수됩니다.
- 팬텀 참조: 객체가 소멸된 후에 리소스 정리 작업을 수행할 수 있습니다.
- 순환 참조: 객체들이 서로 참조하고 있어도 외부에서 접근할 수 없으면 GC는 순환 참조를 처리하여 수집할 수 있습니다.
이 설명을 바탕으로 GC 동작의 도달 가능성과 참조 유형에 대해 더 명확히 이해할 수 있을 것입니다.
주요 GC 알고리즘
자바는 다양한 GC 알고리즘을 제공하며 각 알고리즘은 특정 시나리오에 최적화되어 있습니다.
Serial GC
- 특징: 단일 스레드로 작동하며 메모리 회수 과정이 단순합니다.
- 적용 예: 작은 애플리케이션이나 단일 스레드 환경에 적합합니다.
bash1-XX:+UseSerialGC
Parallel GC
- 특징: 여러 스레드를 사용하여 병렬로 가비지 컬렉션을 수행합니다.
- 적용 예: 대규모 애플리케이션에 적합합니다.
bash1-XX:+UseParallelGC
Concurrent Mark-Sweep (CMS) GC
- 특징: 응용 프로그램 스레드와 동시에 GC를 수행하여 짧은 정지 시간을 제공합니다.
- 적용 예: 상호작용이 많은 애플리케이션에 적합합니다.
bash1-XX:+UseConcMarkSweepGC
Garbage-First (G1) GC
- 특징: 힙을 여러 개의 영역으로 나누어 관리하며 병렬 및 동시 작업을 통해 성능을 최적화합니다.
- 적용 예: 대규모 힙과 예측 가능한 정지 시간을 요구하는 환경에 적합합니다.
bash1-XX:+UseG1GC
ZGC 및 Shenandoah GC
- 특징: 매우 낮은 지연 시간을 목표로 설계되었으며 매우 큰 힙 사이즈를 지원합니다.
bash1-XX:+UseZGC 2-XX:+UseShenandoahGC
GC의 예시와 영향
- 웹 애플리케이션: 높은 요청 처리량과 짧은 응답 시간을 요구하므로, G1 GC 또는 CMS GC와 같은 짧은 정지 시간을 제공하는 GC가 적합합니다.
- 빅데이터 애플리케이션: 대규모 힙을 필요로 하며, GC의 처리량이 중요하므로 Parallel GC가 적합할 수 있습니다.
- 게임 및 실시간 애플리케이션: 매우 짧은 정지 시간이 필요하므로 ZGC나 Shenandoah GC가 적합합니다.
🔍 GC 로그 분석 예시
GC 로그를 분석하면 애플리케이션의 메모리 사용 패턴과 GC의 성능을 파악할 수 있습니다. 자바는 -Xlog:gc 옵션을 통해 GC 로그를 활성화할 수 있습니다.
예시 로그:
[0.016s][info][gc] Using G1
[0.018s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 512M->256M(1024M) 15.123ms
[0.034s][info][gc] GC(1) Pause Full (Normal) (G1 Humongous Allocation) 256M->128M(1024M) 45.678ms
해석:
- Pause Young: Young Generation에서 발생한 가비지 컬렉션.
- Pause Full: 전체 힙에서 발생한 가비지 컬렉션.
- Evacuation Pause: 객체를 이동시키는 동안 발생한 정지 시간.
- Humongous Allocation: 매우 큰 객체의 할당으로 인한 정지 시간.
결론
자바의 가비지 컬렉션은 메모리 관리를 자동화하여 개발자가 메모리 할당과 해제에 대한 부담을 덜어줍니다. 다양한 GC 알고리즘과 옵션을 통해 애플리케이션의 특성에 맞게 메모리 관리를 최적화할 수 있습니다. 효과적인 GC 관리와 튜닝은 애플리케이션의 성능과 안정성을 높이는 데 중요한 역할을 합니다.