JAVA의 GC

최정환·2024년 10월 6일
0
post-thumbnail

자바의 메모리 관리(GC) 방식 이해하기

자바는 객체 지향 프로그래밍 언어로 개발자들이 객체를 생성하고 조작하는 데 집중할 수 있도록 메모리 관리의 많은 부분을 자동화합니다.

이 자동 메모리 관리는 주로 가비지 컬렉션(Garbage Collection, GC)을 통해 이루어집니다.

같은 GC를 가지고 있는 언어의 대표적인 예는 C#, Python, JS, GO 등이 있습니다.


자바 메모리 구조

자바 애플리케이션이 실행될 때, JVM(Java Virtual Machine)은 다양한 메모리 영역을 할당하여 객체를 저장하고 관리합니다. 주요 메모리 영역은 다음과 같습니다

  • 힙(Heap): 모든 객체가 저장되는 메모리 영역입니다. 힙은 다시 Young GenerationOld Generation으로 나뉩니다.
  • 메소드 영역(Method Area): 클래스 정보, 메소드, 정적 변수 등이 저장되는 영역입니다.
  • 스택(Stack): 각 스레드마다 독립적으로 할당되며 메소드 호출과 로컬 변수가 저장됩니다.
  • 프로그램 카운터(Program Counter Register): 각 스레드가 현재 실행 중인 명령의 주소를 가리킵니다.
  • 네이티브 메모리(Native Memory): JVM 외부에서 사용하는 메모리로 네이티브 라이브러리나 JNI(Java Native Interface)를 통해 사용됩니다.

힙(Heap)의 구조:

  1. Young Generation (영 세대):
    Eden Space: 새로 생성된 객체가 처음 할당되는 공간입니다. 대부분의 객체가 여기에 저장됩니다.
    Survivor Space: Eden Space에서 살아남은 객체가 이동하는 영역입니다. 두 개의 Survivor Space(S0, S1)가 번갈아 사용됩니다.
    • Young Generation에서 주기적으로 Minor GC가 발생하여 살아남은 객체는 Survivor Space로 이동하고 그렇지 않은 객체는 메모리에서 해제됩니다.
  2. 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의 주요 역할

  1. 메모리 회수: 사용되지 않는 객체의 메모리를 회수하여 재사용할 수 있게 합니다.
  2. 메모리 누수 방지: 불필요한 객체가 메모리에 남아 있는 것을 방지합니다.
  3. 애플리케이션 성능 향상: 메모리 관리의 부담을 줄여 애플리케이션의 성능을 최적화합니다.

GC의 동작 원리

GC는 주로 다음과 같은 단계로 동작합니다:

  1. 마크(Mark): 도달 가능한 객체를 식별하여 표시합니다.
  2. 스위프(Sweep): 마크되지 않은 객체를 메모리에서 해제합니다.
  3. 컴팩트(Compact): 메모리 단편화를 줄이기 위해 객체를 재배치합니다. (일부 GC 알고리즘에서만 사용)

🔍 도달 가능성 판단

도달 가능성은 객체가 여전히 참조되고 있는지 여부에 따라 결정됩니다. 객체가 더 이상 참조되지 않으면 그 객체는 가비지 컬렉션의 대상이 됩니다. 참조의 유형에는 강한 참조(Strong Reference), 약한 참조(Weak Reference), 순환 참조 등이 있습니다.

객체 도달 가능성의 정의

도달 가능성(Reachability)은 객체가 프로그램 실행 중에서 여전히 참조되고 있는지 여부를 의미합니다. 자바의 가비지 컬렉터는 특정 객체에 대해 더 이상 참조가 존재하지 않으면 그 객체를 가비지로 간주하고 메모리에서 회수합니다.

참조 유형(Reference Types)

  1. 강한 참조(Strong Reference):

    • 가장 일반적인 참조 유형입니다.
    • 객체가 강하게 참조되고 있으면 가비지 컬렉터가 절대로 그 객체를 회수하지 않습니다.
    • 예시:
      MyObject obj = new MyObject(); // 강한 참조
    • obj가 참조를 유지하는 한 이 객체는 절대 GC의 대상이 되지 않습니다.
  2. 약한 참조(Weak Reference):

    • 객체가 약하게 참조되고 있으면 GC는 그 객체를 언제든지 회수할 수 있습니다.
    • 약한 참조는 주로 캐시와 같은 메모리 민감한 데이터 구조에서 사용됩니다.
    • 예시:
      WeakReference<MyObject> weakRef = new WeakReference<>(new MyObject());
    • weakRef는 약한 참조이므로, 강한 참조가 없는 상태에서는 GC가 이 객체를 수집할 수 있습니다.
  3. 소프트 참조(Soft Reference):

    • 약한 참조와 유사하지만, 메모리가 부족할 때만 GC에 의해 회수됩니다.
    • 소프트 참조는 캐시와 같은 상황에서 사용됩니다. 메모리 부족 시에만 회수되므로 비교적 더 오래 살아남을 수 있습니다.
    • 예시:
      SoftReference<MyObject> softRef = new SoftReference<>(new MyObject());
  4. 팬텀 참조(Phantom Reference):

    • 객체가 소멸된 후에도 특정 작업(예: 리소스 정리)을 수행해야 할 때 사용됩니다.
    • 팬텀 참조 객체는 GC가 객체를 회수한 후에야 작동되며, 팬텀 참조는 해당 객체의 직접 접근이 불가능합니다.
    • 예시:
      PhantomReference<MyObject> phantomRef = new PhantomReference<>(new MyObject(), referenceQueue);

순환 참조(Circular Reference)

순환 참조는 객체들이 서로를 참조하는 경우를 말합니다. 순환 참조가 있어도 외부에서 더 이상 해당 객체들을 참조하지 않으면 GC는 이 객체들을 회수할 수 있습니다.

  • 예시: A 객체가 B 객체를 참조하고, B 객체가 다시 A 객체를 참조하는 구조일 때, A와 B 모두 다른 외부에서 참조되지 않는다면 GC는 이를 순환 참조라고 간주하고 수집할 수 있습니다.

GC의 도달성 판별 과정

가비지 컬렉션이 작동할 때, JVM은 GC 루트(GC Roots)라고 불리는 특별한 참조 집합에서 출발하여 객체 그래프를 탐색합니다. 이 과정에서 GC는 도달 가능한 객체는 계속 유지하고, 도달할 수 없는 객체는 가비지로 간주하여 메모리에서 제거합니다.

GC 루트로 간주되는 것들:

  • 스택의 로컬 변수: 현재 실행 중인 메소드의 변수들.
  • 정적 필드: 클래스의 정적 변수들.
  • 활성 스레드: 현재 실행 중인 스레드.

전체적인 요약:

  1. 도달 가능성: GC는 참조되지 않는 객체(더 이상 도달할 수 없는 객체)를 수집합니다.
  2. 참조 유형:
    • 강한 참조: GC가 절대 회수하지 않습니다.
    • 약한 참조: GC는 이 객체를 언제든지 회수할 수 있습니다.
    • 소프트 참조: 메모리가 부족할 때만 회수됩니다.
    • 팬텀 참조: 객체가 소멸된 후에 리소스 정리 작업을 수행할 수 있습니다.
  3. 순환 참조: 객체들이 서로 참조하고 있어도 외부에서 접근할 수 없으면 GC는 순환 참조를 처리하여 수집할 수 있습니다.

이 설명을 바탕으로 GC 동작의 도달 가능성과 참조 유형에 대해 더 명확히 이해할 수 있을 것입니다.


주요 GC 알고리즘

자바는 다양한 GC 알고리즘을 제공하며 각 알고리즘은 특정 시나리오에 최적화되어 있습니다.

Serial GC

  • 특징: 단일 스레드로 작동하며 메모리 회수 과정이 단순합니다.
  • 적용 예: 작은 애플리케이션이나 단일 스레드 환경에 적합합니다.
-XX:+UseSerialGC

Parallel GC

  • 특징: 여러 스레드를 사용하여 병렬로 가비지 컬렉션을 수행합니다.
  • 적용 예: 대규모 애플리케이션에 적합합니다.
-XX:+UseParallelGC

Concurrent Mark-Sweep (CMS) GC

  • 특징: 응용 프로그램 스레드와 동시에 GC를 수행하여 짧은 정지 시간을 제공합니다.
  • 적용 예: 상호작용이 많은 애플리케이션에 적합합니다.
-XX:+UseConcMarkSweepGC

Garbage-First (G1) GC

  • 특징: 힙을 여러 개의 영역으로 나누어 관리하며 병렬 및 동시 작업을 통해 성능을 최적화합니다.
  • 적용 예: 대규모 힙과 예측 가능한 정지 시간을 요구하는 환경에 적합합니다.
-XX:+UseG1GC

ZGC 및 Shenandoah GC

  • 특징: 매우 낮은 지연 시간을 목표로 설계되었으며 매우 큰 힙 사이즈를 지원합니다.
-XX:+UseZGC
-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 관리와 튜닝은 애플리케이션의 성능과 안정성을 높이는 데 중요한 역할을 합니다.


0개의 댓글