Nick Dev

[JAVA] Heap 구조, GC 동작 방식 그리고 GC 알고리즘 5가지 본문

Java

[JAVA] Heap 구조, GC 동작 방식 그리고 GC 알고리즘 5가지

Nick99 2024. 12. 11. 12:48
반응형

Garbage Collector 역할

  1. 메모리 할당
  2. 사용 중인 메모리 인식
  3. 사용하지 않는 메모리 인식

GC 종류 2가지

Minor GC

  • Eden 영역이 꽉 차게 되면 시작되는 GC
    • Young generation에서 발생하는 GC
  • 과정
    1. Eden이 처음 꽉 차게 되면 Minor GC 이후 살아남은 객체들 S0로 이동(S0로 가정)
      1. S1은 empty
      2. S0에 들어가기에 너무 큰 객체면 바로 Old영역으로 promotion
    2. 또 Eden이 꽉 차게 되면 Minor GC 발생
      1. 이때, Eden + S0 에서 살아남은 객체들이 S1으로 이동
      2. S0가 empty
    3. 1, 2의 과정 계속 반복
      1. 이렇게 Survivor 영역 사이 이동할 때마다 age 증가
    4. 객체가 특정 age 넘으면 Old generation으로 promotion
      1. HotSpot JVM의 경우 default = 15
      2. -XX:MaxTenuringThreshold 옵션으로 이 연령 한계를 설정 가능

🌟 Survivor 영역이 2개로 나눠져서 한 쪽은 항상 비어있는 이유 🌟

  • 디스크 조각 모음 기능과 유사하다
  • 한 공간을 몰아서 정리하지 않으면 빈 공간이 많이 생겨, 전체 메모리 공간은 많아도 파편화 되서 실질적으로 메모리를 할당하지 못할 수 있다.
  • GC로 메모리 청소하는김에 한 공간으로 몰아서 순차적으로 정리해놓으면 나중에 또 접근할 때 성능이 좋다.
  • 그래서 이렇게 순차적으로 정리를 하기 위해서 하나의 Survivor 공간을 비워두고 거기에 정리해서 넣는 것

Old generation으로 promotion하는 경우 2가지

  1. S0 ↔ S1 이동할 때마다 증가하는 age가 임계값을 넘을 때
  2. 객체의 크기가 Survivor 영역보다 클 때

Major GC

  • Full GC라고도 함
  • Old generation이 꽉 차면 발생
  • unreachable한 객체들 정리

Heap 영역

  • 가비지 컬렉터가 인식하고 할당하는 영역

구조

  • Young, Old, Perm 영역
    • Perm 영역은 JDK 8부터 사라짐

Young 영역

  • Eden 영역, S0, S1 영역으로 구성
    • S는 Survivor의 약자
    • S0, S1 사이에 우선순위 없음

Eden 영역

  • 객체가 생성될 때, 가장 왼쪽에 있는 Eden 영역에 객체가 지정
  • Eden 영역에 데이터(생성된 객체들)가 꽉 차면 → minor gc 발생
    • 이때, 살아남은 객체들은 S0 또는 S1 영역으로 옮겨가야된다
    • S0로 이동했다고 가정

Survivor0 or 1 영역

  • Eden 영역이 꽉 차게 되면 객체들이 저장되는 공간
  • S0와 S1 둘 중 한 공간은 항상 비어 있다

Old 영역

  • Young generation에서 promotion되어 올라온 객체들
  • Old가 꽉 차면 Major GC 발생
    • 더 이상 참조되지 않는 객체들을 정리

5가지 GC 알고리즘

1. Serial Collector

  • Minor GC 수행하고 Major GC 수행
  • 이렇게 serial하게 처리
  • 1개 CPU 사용
  • 이렇게 GC가 수행될 때, 애플리케이션 수행이 중지 = stop the world
  • Major GC는 Mark-Sweep-Compact 알고리즘으로 동작
    • reachable한 객체 mark → mark 안된 것들 sweep → 살아남은 것들 Old의 맨 왼쪽에 compact
    • 한쪽으로 압축하는 과정을 통해 memory fragmentation을 줄일 수 있다 (카페 예시)

정리

  • 싱글 코어 or 싱글 thread일 때 사용하지만, 요즘 기기에는 이런 경우 없음 → 잘 안쓴다

2. Parallel Collector

Minor GC

  • Parallel하게 처리
    • Minor GC 수행할 때, 여러 thread가 병렬로 수행
    • 이유 : Eden이 자주 꽉차서 Young 영역 GC가 빈번하게 발생 → 여러 thread가 처리하면 GC 작업 시간 감소 = STW 시간 감소 → 애플리케이션 처리량 증가

Major GC

  • Mark-Sweep-Compact 알고리즘 사용(아직 Old 영역은 하나의 thread로 처리)

정리

  • 멀티 코어, 프로세서 환경에서 사용
  • Minor GC는 Parallel하게 처리하지만 Major GC는 single Thread로 처리

3. Parallel Compacting Collector

Minor GC

  • Parallel Collector와 동일한 알고리즘으로 동작
    • 즉, Paralle하게 처리

Major GC

  • 새로운 Mark-Summary-Compact 알고리즘 사용
  • Summary 부분이 기존 Sweep 부분과 다른 점 2가지
    1. 여러 thread가 Old 영역을 분리해서 훑는다
    2. 이전에 Major GC에 의해 Compacting된 영역을 별도로 훑는다
      • 또 훑는 이유는 이 영역에 대해 추가적으로 압축이 필요한지 체크해 메모리 단편화를 감소시키기 위해서

정리

  • 이젠 Minor, Major GC 모두가 멀티 쓰레드 방식으로 GC 수행

4. Concurrent Mark-Sweep Collector(CMS)

Minor GC

  • Parallel Collector와 동일한 알고리즘으로 동작
  • 즉, Paralle하게 처리

Major GC

  • CMS GC 알고리즘 사용 (4단계)
  1. Initial Mark(STW)
    • GC Roots로부터 직접 접근 가능한 객체들을 마킹
      • stack 내의 local 변수들의 객체, 정적 변수들
      • 얘네들만 marking하고 넘어감
  2. Concurrent Mark
    • Initial Mark에서 마킹한 애들 따라가면서 참조하고 있는 객체들 따라가면서 확인
    • 다른 thread와 동시에 진행
  3. Remark(STW)
    • Concurrent Mark가 다른 thread와 동시에 진행하기에 수행하는 동안 객체 참조가 끊길 수 있기에 다시 확인하는 단계
    • 결국 reachable한 객체들만 marking
  4. Concurrent Sweep
    • marking되지 않은 객체들 모두 정리
    • 다른 thread들과 동시에 수행

정리

  • Minor, Major GC 둘다 멀티 thread 지원
    • Minor GC는 Parallel Collector와 동일한 알고리즘
    • Major GC는 CMS 알고리즘 사용 → STW 매우 짧지만, Compaction이 없어서 메모리 단편화 발생

5. Garbage First Collector(G1)

  • 앞선 GC들과 아예 다름
    • Young, Old 주소가 물리적으로 Linear하게 있지 않고 바둑판 형식으로 구성

5번 GC
5번 GC

1~4 GC들

1~4 GC들


  • JDK 9 이후 디폴트 GC로 권장

Young영역 GC (Minor GC)

  1. 특정 region들을 Young 영역으로 선정
  2. Eden region 꽉차면 GC 발생(STW)
    • 병렬로 진행
  3. Young 영역의 객체들 스캔해서 reachable한 객체 식별 후 Survivor 영역으로 옮김
    • 이때 age가 꽉찬 객체는 Old 영역으로
    • 옮겨지고 난 영역은 빈 영역은 Eden, Survivor 영역으로 재활용

Old영역 GC (CMS의 GC와 유사)

  • 특정 점유율에 도달하면 CMS 시작
  1. Initial Mark (STW)
    • Old영역에 있는 객체를 참조하고 있는 Survivor 영역에 대해 Mark
  2. Root region scanning
    • Survivor 영역을 스캔해 Old 영역에 대한 참조를 찾는다
    • Young GC 발생 전에 이 단계 완료되어야 함
    • 애플리케이션 수행과 동시에 진행
  3. Concurrent Mark
    • 전체 Heap 영역에 살아있는(reachable한) 객체 찾기
    • 애플리케이션 수행과 동시에 진행
    • Young GC 발생하면 잠깐 멈춘다
  4. Remark (STW)
    • STW 한 후, Heap에서 살아있는 객체들 표시
    • SATB(snapshot-at-the-beginning) 알고리즘 사용
      • CMS GC에서 사용하는 방식보다 빠름
  5. Cleaning (STW)
    • 살아있는 객체와 빈 구역 식별 → 필요없는 객체(unreachable)들 지운다
    • 빈 구역들 초기화 ( → 재사용 )
  6. Copy (STW)
    • Compaction 진행 → 이거 때문에 CMS의 개선안이라고 할 수 있는 것 같음
      • 살아 있는 객체들 빈 구역으로 모은다
      • 메모리 단편화 줄일 수 있다

Snapshot-At-The-Beginning

  1. 마킹 시작 시점에 스냅샷 캡처
    • 가비지 컬렉션의 초기 표시 단계에서, 모든 살아 있는 객체들을 "표시"
    • 이 시점에서의 힙 상태가 스냅샷으로 간주
  2. 애플리케이션의 변경사항 로깅
    • 객체를 참조하는 것에서 참조하지 않는 것으로 변경)할 때, 이러한 변경 사항을 로그에 기록
  3. 동시 마킹 단계
    • 스냅샷 시점에서 살아 있었던 객체와 그 이후 로그에 기록된 참조 변경사항을 고려하여 살아 있는 객체를 식별

언제 사용?

  • 대용량 Heap일 때
    • G1은 Heap을 여러 region으로 나눠 관리하기에 Heap이 매우 크면 효율적으로 관리할 수 있다
  • 멀티 프로세서, 코어 환경
  • 메모리 단편화 줄이고 싶을 때

정리

  • Heap 메모리가 바둑판처럼 되어 있어 Eden, Survivor, Old가 물리적으로 Linear하게 있지 않다
  • Old영역 GC는 CMS Collector의 Old 영역 GC와 유사하지만
    • Copy단계에서 Compaction을 통해 메모리 단편화를 줄여준다
    • Remark 단계에서 SATB 알고리즘을 써서 더 빨리 reachable 객체들 표시한다

간단하게 정리


Young GC 알고리즘 Old (mark-sweep-compact)
single thread Serial single thread
multi thread Parallel single thread
multi thread Parallel Compacting multi thread
multi thread Concurrent Mark-Sweep CMS(multi thread)
아마 multi thread..? G1 CMS와 유사(SATB, Copy 단계)

참조 자료
G1GC Garbage Collector에 대해 알아보기 - 1

반응형