Java의 Garbage Collection

break 없는 while loop·2024년 11월 25일
post-thumbnail

1. GC(Garbage Collection)란?

Java의 Garbage Collection(GC)은 프로그래밍 언어에서 메모리 관리를 자동화하는 기능으로, 개발자가 명시적으로 메모리를 해제하지 않아도 사용하지 않는 객체를 식별하고 제거하여 메모리 누수를 방지한다. 이것은 JVM(Java Virtual Machine)의 메모리 관리 시스템의 핵심 기능 중 하나이다.

즉, C처럼 free 같은 걸 할 필요 없다.

2. GC의 주요 원리

  1. Heap 메모리 구조:
    • Young Generation:
      • 새로 생성된 객체가 저장된다.
      • 크기가 작고 수명이 짧은 객체들이 주로 여기에 존재한다.
      • Young Generation은 다시 두 영역으로 나뉜다.
        • Eden Space: 객체가 처음 생성되는 곳.
        • Survivor Spaces: Eden에서 살아남은 객체들이 복사되어 이동.
    • Old Generation (Tenured):
      • Young Generation에서 오래 살아남은 객체가 이동.
      • 크기가 크고 수명이 긴 객체들이 주로 여기에 존재.
    • Permanent Generation (JDK 8 이전):
      • 클래스 메타데이터와 같은 정보가 저장되던 영역.
      • JDK 8 이후에는 Metaspace로 대체되어 Heap 외부의 네이티브 메모리를 사용.
  2. GC의 주요 과정:
    • JVM은 참조되지 않거나 더 이상 사용되지 않는 객체를 식별하여 제거한다.
    • 객체가 더 이상 참조되지 않는 상태를 확인하는데 Reachability Analysis(도달성 분석)를 사용한다.
      • GC Root에서 객체를 따라가며 유효한 객체인지 판단.

3. GC의 유형

  1. Serial GC:

    • 단일 스레드로 실행되는 GC
    • 가볍고 메모리 소규모 애플리케이션에 적합
    • Young과 Old Generation 모두 Stop-the-World(STW) 이벤트를 유발
  2. Parallel GC:

    • 여러 스레드를 사용하여 GC 작업을 수행
    • Young Generation 수집에 적합하며, 멀티코어 시스템에서 성능이 향상됨
  3. CMS (Concurrent Mark-Sweep) GC:

    • Old Generation의 수집을 멀티스레드로 처리
    • 애플리케이션 정지를 최소화하려는 목적
    • Mark-Sweep 방식:
      1. Mark 단계: 사용 중인 객체를 식별
      2. Sweep 단계: 사용되지 않는 객체를 제거
  4. G1 (Garbage-First) GC:

    • JDK 9 이후 기본 GC
    • 메모리를 Region이라는 고정 크기의 블록으로 나누어 작업
    • Young과 Old Generation을 동시에 관리
    • 목표: Stop-the-World 시간을 최소화
  5. ZGC (Z Garbage Collector):

    • 매우 짧은 STW 시간을 제공
    • 수백 GB 크기의 메모리를 처리 가능
    • JDK 11 이후 사용 가능
  6. Shenandoah GC:

    • 낮은 레이턴시를 목표로 설계
    • 객체 이동을 애플리케이션 실행과 병렬로 수행

4. GC 튜닝 및 관리

  • JVM 옵션으로 GC 동작을 조정할 수 있다.
    • -XX:+UseG1GC: G1 GC 활성화
    • -Xms<size>: 초기 Heap 크기 설정
    • -Xmx<size>: 최대 Heap 크기 설정
    • -XX:MetaspaceSize=<size>: Metaspace 크기 설정 (JDK 8 이후)

5. GC의 장점

  1. 메모리 누수를 방지
  2. 개발자가 명시적으로 메모리 해제를 관리할 필요가 없음
  3. 안정적이고 예측 가능한 메모리 관리 제공

6. GC의 단점

  1. Stop-the-World로 인해 애플리케이션이 일시 정지
  2. 대규모 애플리케이션에서는 GC 튜닝이 필수
  3. 높은 메모리 사용 시 성능 저하 가능

7. GC 튜닝 Tip

1. 초기 Heap 크기와 최대 Heap 크기 설정 Tip

  • 초기 Heap 크기 (-Xms)
    초기 크기와 최대 크기를 동일하게 설정한다면(즉, -Xms-Xmx를 동일하게 설정) JVM이 Heap 크기를 동적으로 조정하는 과정을 생략할 수 있어, 성능 오버헤드를 줄일 수 있다. (ex. -Xms1024m -Xmx1024m)
    반면, 충분히 큰 크기로 설정할 경우 애플리케이션이 초기 로드 시 많은 객체를 생성하거나, 데이터 캐싱을 사용하는 경우에 GC 발생을 줄일 수 있다.

  • 최대 Heap 크기
    시스템 전체 RAM을 기준으로 가용 메모리의 50~75%로 설정할 경우 애플리케이션 외에도 OS와 다른 프로세스가 메모리를 사용할 수 있기 때문에 여유가 생긴다. (ex. 시스템 RAM이 16GB라면 -Xmx8g ~ -Xmx12g 정도가 적합)
    애플리케이션이 메모리를 점진적으로 많이 사용하는 경우, 과도한 Heap 크기를 설정하면 메모리 누수가 감지되기 어려워질 수 있다. 최적화를 위해 메모리 프로파일링 도구(ex. VisualVM, JProfiler)를 사용하자.

2. GC와 연계한 설정

  • G1 GC를 사용하는 경우:
    • G1 GC는 큰 Heap 크기에서 효율적으로 동작한다. 따라서 최대 Heap 크기를 4GB 이상으로 설정하는 것이 일반적이다.
    • 옵션 예시:
			-Xms4g -Xmx9g -XX:+UseG1GC         
  • GC 튜닝과 연계:
    • 애플리케이션의 응답 시간을 최적화하려면 GC 동작과 Heap 크기를 조정해야 한다.
    • G1 GC 예시:
			-Xms4g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200

3. 애플리케이션 특성에 따른 설정

  • 데이터 집중형 애플리케이션
    • 대량의 데이터를 처리하거나 캐시를 사용하는 경우, Heap 크기를 더 크게 설정
    • 예: -Xms8g -Xmx16g
  • 경량 애플리케이션
    • 메모리 사용량이 적다면 작은 크기로 설정
    • 예: -Xms512m -Xmx1g

4. 모니터링 기반 설정

  • 모니터링 도구 사용:
    • JVM의 메모리 사용량을 모니터링하여 적절한 크기를 설정하자
    • 도구: VisualVM, JConsole, Prometheus + Grafana
  • GC 로그 분석:
    • GC 로그를 활성화하고, GC 빈도와 Pause Time을 분석하여 메모리 크기를 조정하자
		-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

5. 실전 팁

  • 메모리 부족으로 인한 문제 방지:
    • JVM의 OOM(Out of Memory)을 방지하려면 가용 시스템 메모리를 초과하지 않는 범위 내에서 설정해야 한다
  • 테스트 환경에서 검증:
    • 설정한 값이 실제로 애플리케이션 요구사항을 충족하는지 테스트하자
    • 부하 테스트 도구: JMeter, Gatling 등
  • 멀티 JVM 인스턴스:
    • 단일 JVM 대신 여러 JVM을 실행하는 경우, Heap 크기를 작게 설정하여 메모리를 나누자

8. JDK 버전 별 GC 기본값 및 변경 사항

JDK 버전기본 GC주요 변경 사항신규/개선된 GC
JDK 7Parallel GC- 기본 GC는 Parallel GC.
- CMS GC 및 Serial GC 사용 가능.
-
JDK 8Parallel GC- Metaspace 도입 (Permanent Generation 제거).
- G1 GC 성능 개선.
G1 GC
JDK 9Parallel GC- Unified Logging Framework으로 GC 로깅 개선.
- G1 GC 성능 대폭 개선.
- ZGC 도입 (실험적).
ZGC (실험적)
JDK 10Parallel GC- Thread-Local Handshake 도입 (특정 스레드만 중단 가능).-
JDK 11G1 GC- 기본 GC가 G1 GC로 변경.
- ZGC 도입 (실험적).
- CMS GC 사용 중단 권고 (Deprecated).
ZGC
JDK 12G1 GC- G1 GC에서 Abortable Mixed Collection 도입.
- Shenandoah GC 도입 (실험적).
Shenandoah GC (실험적)
JDK 13G1 GC- ZGC의 Heap 크기 제한(4TB) 제거.
- G1 GC의 사용률 기반 Region 분할 개선.
-
JDK 14G1 GC- ZGC 및 Parallel GC 성능 최적화.
- G1 GC의 Adaptive 가비지 분류 개선.
-
JDK 15G1 GC- ZGC 안정화 (정식 기능으로 전환).
- Shenandoah GC 최적화.
-
JDK 16G1 GC- ZGC 최대 Heap 크기를 16TB로 확장.
- G1 GC의 Young Generation 정리 병렬화.
-
JDK 17G1 GC- LTS(Long-Term Support) 릴리즈.
- ZGC와 Shenandoah GC 성능 최적화.
- CMS GC 완전히 제거.
-
JDK 18G1 GC- ZGC에서 내부 메타데이터 GC 지원 추가.
- Parallel GC에서 배리어 성능 개선.
-
JDK 19G1 GC- ZGC에서 이동 중 객체 참조 안정성 강화.
- G1 GC의 pause time 개선.
-
JDK 20G1 GC- ZGC와 G1 GC 간의 Stop-the-World 최적화.
- G1 GC Region 재배치 성능 개선.
-
JDK 21G1 GC- Virtual Threads와 GC 최적화.
- ZGC 및 Shenandoah GC에서 가비지 수집 병렬성 강화.
-

주요 GC 요약

  1. Parallel GC:
    • Throughput 최적화에 적합.
    • 멀티스레드로 GC 수행.
  2. G1 GC:
    • JDK 9에서 개선 및 JDK 11부터 기본 GC.
    • 낮은 레이턴시와 짧은 Stop-the-World 시간 제공.
  3. ZGC:
    • JDK 11에서 실험적 도입, JDK 15에서 정식 기능으로 안정화.
    • 초저지연(10ms 미만), 대규모 메모리 지원.
  4. Shenandoah GC:
    • JDK 12에서 실험적 도입.
    • 낮은 레이턴시 제공, G1 GC 대체 가능.

지연 시간과 처리량 간의 균형이 잘 맞는 G1 GC를 주로 사용하고,
메모리 사용량이 거의 없다면 Serial GC를,
초저지연이 필요하다면 ZGC, Shenandoah GC를 생각해보자.

profile
프로그래밍 지식 아카이브용

0개의 댓글