[JAVA] 공식문서로 공부하는 Garbage Collection - Ch 3. Garbage Collector Implementation

예름·2025년 3월 23일

Java

목록 보기
6/9

Oracle의 공식문서 HotSpot Virtual Machine Garbage Collection Tuning Guide을 참고했습니다.

📍 세대별 가비지 컬렉션 (Generational Garbage Collection)
📍 세대 (Generations)
📍 성능 고려 사항 (Performance Considerations)
📍 처리량(Throughput) 및 메모리 사용량(Footprint) 측정

📍 개요

Java SE 플랫폼의 강점 중 하나는 메모리 할당과 가비지 컬렉션의 복잡성을 개발자로부터 숨겨준다는 점이다.

그러나 가비지 컬렉션이 주요한 병목 현상이 될 경우, 구현 방식의 일부를 이해하는 것이 유용하다. 가비지 컬렉터는 애플리케이션이 객체를 사용하는 방식에 대한 가정을 바탕으로 동작하며, 이러한 가정은 조정 가능한 매개변수(튜닝 가능한 파라미터)로 반영된다. 이를 조정함으로써 추상화의 장점을 유지하면서도 성능을 개선할 수 있다.


🔎 세대별 가비지 컬렉션 (Generational Garbage Collection)

객체는 실행 중인 프로그램에서 다른 살아있는 객체의 어떤 참조(reference)에서도 접근할 수 없을 때 가비지(garbage)로 간주되며, VM은 이 객체의 메모리를 재사용할 수 있다.

이론적으로 가장 직관적인 가비지 컬렉션 알고리즘은 매번 실행될 때마다 모든 접근 가능한 객체를 반복적으로 확인하는 것이다. 남겨진 객체들은 가비지로 처리된다. 그러나 이 방식은 실행 시간의 복잡도가 살아있는 객체 수에 비례하기 때문에, 많은 데이터를 유지하는 대규모 애플리케이션에서는 비효율적이다.

Java HotSpot VM은 여러 가비지 컬렉션 알고리즘을 포함하고 있으며, 세대별 가비지 컬렉션(Generational Collection) 기법을 사용한다.

순진한(naive) 가비지 컬렉션은 매번 모든 살아있는 객체를 검사하지만, 세대별 가비지 컬렉션은 애플리케이션에서 객체가 사용되는 특정 패턴을 활용하여 불필요한 작업을 최소화한다.

이러한 패턴 중 가장 중요한 것은 “약한 세대 가설(Weak Generational Hypothesis)” 이다.
이는 대부분의 객체는 생성된 후 아주 짧은 시간 내에 사용이 끝난다는 점을 의미한다.

그림은 객체의 수명 분포를 보여준다.

  • x축: 객체의 수명 (할당된 바이트 수 기준)
  • y축: 해당 수명을 가진 객체의 총 바이트 수

그래프의 왼쪽에 있는 가파른 피크(peak) 는 대부분의 객체가 생성 직후 곧바로 소멸됨을 의미한다.

예를 들어, 반복자(iterator) 객체는 보통 하나의 루프 동안만 살아 있다가 제거된다.

반면, 일부 객체는 오래 살아남아 그래프의 오른쪽으로 분포가 늘어진다.
예를 들어, 초기화 시점에 생성된 객체는 JVM이 종료될 때까지 유지될 수 있다.

이 두 극단 사이에는 중간 정도의 계산(computation)이 지속되는 동안만 유지되는 객체들이 존재하며, 그래프에서 초기 피크 이후의 작은 봉우리가 이에 해당한다.

일부 애플리케이션은 다른 분포를 보일 수 있지만, 대부분의 애플리케이션에서는 이와 비슷한 형태를 가진다.

결과적으로, 대부분의 객체는 금방 소멸한다는 점을 이용하면 효율적인 가비지 컬렉션이 가능하다.


🔎 세대 (Generations)

가비지 컬렉션을 최적화하기 위해 JVM은 객체의 수명에 따라 메모리를 세대(Generation) 단위로 관리한다.
각 세대는 일정 크기의 메모리 풀(pool)이며, 각 세대가 가득 차면 해당 세대에서 가비지 컬렉션이 수행된다.

📌 세대별 메모리 구조

1. Young Generation (젊은 세대)

  • 대부분의 객체는 Young Generation에서 생성되며, 여기서 대부분의 객체가 빠르게 소멸된다.
  • Young Generation이 가득 차면 “Minor GC(마이너 GC)“가 발생, 이때 Old Generation의 객체는 영향을 받지 않는다.
  • Minor GC는 살아있는 객체가 적을수록 빠르게 수행되므로, 대부분의 Young Generation이 죽은 객체로 가득 차 있다면 매우 효율적이다.

2. Old Generation (오래된 세대)

  • Young Generation에서 살아남은 일부 객체는 Old Generation으로 이동한다.
  • Old Generation이 가득 차면 “Major GC(메이저 GC)” 또는 “Full GC(전체 GC)“가 수행, 이 과정은 Young Generation보다 오래 걸린다.
  • Major GC는 전체 힙(Heap)을 대상으로 수행되므로, 애플리케이션의 성능에 큰 영향을 미칠 수 있다.

📌 Young Generation 내부 구조

  • Young Generation은 Eden Space + 두 개의 Survivor Space(S0, S1)로 구성된다.
  • 대부분의 객체는 Eden Space에서 생성된다.
  • Minor GC가 발생하면 Eden과 하나의 Survivor Space(S0 or S1)에 남아있는 살아있는 객체가 다른 Survivor Space로 복사됨.
  • 이 과정이 일정 횟수(Threshold) 이상 반복되면, 객체는 Old Generation으로 이동(aging 과정).

🔎 성능 고려 사항 (Performance Considerations)

가비지 컬렉션의 주요 성능 지표는 처리량(Throughput)지연시간(Latency) 이다.

📌 Throughput (처리량)

  • 가비지 컬렉션에 사용되지 않고, 실제로 애플리케이션이 실행되는 시간의 비율을 의미한다.
  • 웹 서버처럼 일정 시간 내 최대한 많은 요청을 처리해야 하는 애플리케이션은 처리량이 중요하다.
  • Young Generation을 크게 설정하면 처리량을 높일 수 있지만, 전체적인 메모리 사용량도 증가한다.

📌 Latency (지연시간)

  • 애플리케이션의 응답성을 나타내며, 가비지 컬렉션으로 인한 일시적인 멈춤(pause time)이 영향을 미친다.
  • 예를 들어, 게임이나 실시간 애플리케이션에서는 짧은 GC 일시 정지도 사용자 경험에 부정적인 영향을 줄 수 있다.

트레이드오프 (Trade-off)

  • Young Generation을 작게 설정하면 GC 지연시간이 줄어들지만, 처리량이 감소할 수 있음
  • 반대로 Young Generation을 크게 설정하면 처리량은 증가하지만, GC 일시 정지 시간이 길어질 수 있음

🔎 처리량 및 메모리 사용량 측정 (Throughput and Footprint Measurement)

가비지 컬렉션 성능을 분석하기 위해 JVM 로그를 활용할 수 있다.

📌 기본적인 GC 로그 출력

-verbose:gc

📌 자세한 GC 정보 출력 (-Xlog:gc)

-Xlog:gc*

출력 예시

[10.191s][info][gc] GC(36) Pause Young (G1 Evacuation Pause) 391M->114M(508M) 13.075ms
  • Young GC 후 사용 메모리 변화: 391MB → 114MB
  • 전체 힙 크기: 508MB
  • GC 소요 시간: 13.075ms
profile
안정적인 쳇바퀴를 돌리는 삶

0개의 댓글