Garbage Collection(GC) - (2)

겔로그·2022년 9월 25일
2

Java

목록 보기
3/10
post-thumbnail

개요

이전 시간에는 JDK 8 버전까지 사용했었던 GC들에 알아보는 시간을 가졌습니다. 이번 시간에는 JDK 9 버전 이후로 사용되는 GC에 대해 공부해보는 시간을 가지려고 합니다.
참고: Garbage Collection(GC) - (1)

JDK 8이하 버전의 GC에서는 기존의 JVM에서 Heap 영역을 물리적으로 분리해 Minor GC와 Major GC의 역할이 명확했습니다. 하지만 JDK 9부터 도입된 GC들은 기존 GC들의 문제를 해결하기 위해 다음과 같은 목표들을 설정했습니다.

  • pause의 최소화(STW 시간을 최소화한다.)
  • 빠른 처리 속도

지금부터 새로 생긴 GC들의 동작 과정을 보면서 각 GC에 대한 이해도를 높이고 새롭게 나온 GC들의 성능이 왜 뛰어난지에 대해 알아보는 시간을 가져보겠습니다.

JDK 9버전 이후 생긴 GC

G1 GC(Garbage First)

G1 GC는 JDK 9부터 default GC로 선정되었으며 이는 성능상으로 비교했을 때 타 GC 보다 뛰어난 성능을 보인다는 것을 알 수 있습니다. 그럼 G1 GC가 어떻게 다른 GC들보다 성능이 우수한지 알아보겠습니다. (JDK 9 이전 default GC : Parallel GC)

기존의 Heap 영역을 Young/Old 분리하여 진행하던 GC 방식과 완전히 다르게 진행됩니다.

G1 GC는 CMS GC를 대체하기 위해 고안된 GC이며 대용량의 메모리가 있는 멀티 프로세서 시스템을 위해 제작되었습니다. 빠른 처리 속도를 지원하면서 STW를 최소화합니다. CMS GC보다 효율적으로 동시에 Application과 GC를 진행할 수 있고, 메모리 Compaction 과정까지 지원합니다.

G1인 이유는 Garbage가 존재하는 Heap 영역의 region을 먼저 회수하다 해서 불린 이유이며. 빈 공간 확보를 타 GC보다 더 빨리하여 조기 승격(Young -> Old)를 방지하며 Old generation을 한가하게 만들 수 있는 이점이 존재합니다.

G1 GC의 장점

  • 별도의 STW 없이 여유 메모리를 압축하는 기능을 제공한다.
  • Old 영역 또는 Young 영역을 전부 압축하는 것이 아닌 특정 region에 대한 압축이 진행되기에 보다 효율적이다.
  • CMS에 비해 개선된 알고리즘을 사용하며 Compaction 기능을 제공하고 처리 속도가 더 빠르다.
  • Garbage로 가득찬 region을 빠르게 회수하기 때문에 GC 빈도수가 줄어든다.

G1 GC의 단점

  • OOME를 조심해야 한다.
  • GC가 동작하고도 공간이 부족할 경우 Full GC가 발생하는데 이는 단일 스레드로 처리된다.
  • 작은 Heap 공간을 가질 경우 Full GC가 자주 발생해 성능을 발휘하지 못한다.

G1 GC 동작을 알기 전 알아야할 내용

기존 GC를 요약해보면 다음과 같은 영역으로 이루어져 있습니다.

  • Young 영역 (eden, survivor1, survivor2)
  • Old 영역

G1 GC는 해당 영역들이 하나의 큰 메모리 영역을 region으로 분리해 각각의 영역에 논리적인 개념을 정의합니다.

Region 에 정의하는 논리적 개념 종류

  • Eden: 맨 처음 메모리 할당할 경우 들어가는 영역
  • Survivor: Minor GC 수행시 Eden 영역에서 이동되는 객체들이 이동하는 영역
  • Old: Survivor에서 살아남은 객체들이 이동하는 영역
  • Avaialable / Unused region: 사용하지 않는 영역
  • Humongous region: 객체의 크기가 region영역의 일정 부분을 채울 정도(1/2, 1/3)로 클 경우 해당 영역으로 이동한다

G1 GC Heap 구조

G1 GC는 기존의 Young/Old와 같이 구분된 영역이 아닌 일정 크기의 논리적 단위인 region으로 구분해 각각의 region에 개념적으로 각각의 영역을 정의합니다.

G1 GC의 Cycle

G1 GC는 위 그림처럼 Young-only Phase와 Space Reclamation Phase를 반복한다. 사이클 중 모든 원은 STW가 발생한 것을 나타낸 것이고, 원의 크기에 따라 STW 소요 시간이 달라진다고 생각하면 좋을 것 같습니다.
파란 원은 Minor GC(Evacuation Pause)가 진행함에 따라 STW가 발생한 것이고, 주황 원은 Major GC(Concurrent Cycle)이 진행하면서 객체를 마킹 및 기타 과정을 하기 위해 STW가 발생한 것이고, 빨간 원은 Mixed GC를 진행함에 따라 STW가 발생한 것입니다.

G1 GC 동작 과정

G1 GC의 수행 과정은 크게 세가지로 나누어집니다.

G1 GC 동작 과정

  • Evacuation Pauses(=Minor GC)
  • Concurrent Cycle
  • Mixed GC

이제 각 동작이 어떻게 수행되는지 알아보겠습니다.

Evacuation Pause(=Young GC)

연속되지 않은 메모리 공간에 Young Generation이 Region 단위로 메모리에 할당되어 있습니다.

GC가 실행되면 Young Generation 영역에서 살아있는 객체들은 Survivor Region이나 Old Generation 영역으로 이동시키며 압축을 진행합니다.

최종적으로 이동을 마치면 기존에 Eden 영역에 생성된 객체들 중 살아남은 객체들은 Survivor region으로 이동되거나 Old Generation 영역으로 이동되며 압축하여 다음과 같이 적은 용량을 사용합니다.

Concurrent Cycle

Initial Mark 단계에서는 Old Region에 존재하는 객체들이 참조하는 Survivor Region이 있는지 파악해 마킹하는 단계입니다.
Initial Mark 단계가 완료되면 Initial Mark 단계에서 마킹된 Survivor Region에서 Old Region에 대해 참조하고 있는 객체를 마킹합니다. 해당 내용은 Minor GC가 발생하기 이전에 동작을 완료합니다.

Concurrent Marking 단계에서는 Old Generation 내에 생존해 있는 모든 객체를 마킹합니다. 마킹을 통해 참조하지 않는 객체들을 확인합니다.

Remark 단계에서는 앞서 진행한 Initial Marking 과정에서 확인된 참조되지 않는 객체들을 회수합니다.

Copying/Cleanup 단계에서는 STW가 발생하며, 살아있는 객체의 비율이 낮은 영역 순으로 순차적으로 수거합니다.

Major GC가 끝난 이후, 살아있는 객체가 새로운 Region으로 압축이 되어 이동하니 보다 더 효율적으로 이용이 가능해집니다.

Mixed GC

Mixed GC는 Young 영역과 Old 영역의 Garbage를 수집합니다. 한 번에 Old 영역의 Garbage를 수집하는 것은 비용이 크므로 Mixed GC는 기본적으로 8회 수행합니다.

Mixed GC는 Minor GC에서 수행하는 단계와 동일하지만, 추가로 Old 영역의 Garbage를 수집합니다. 즉, Mixed GC는 Minor GC와 Old 영역의 GC를 혼합한 과정이라고 할 수 있습니다.

Shenandoah GC

Shenandoah GC는 '큰 GC 작업을 적은 횟수로 수행하는 것보다 작은 GC 작업을 여러분 수행하는게 더 좋다'는 개념을 적용해 만들어진 GC 입니다.

CMS가 가진 메모리 단편화 이슈와 G1 GC가 가진 pause 이슈를 해결하였으며 heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요됩니다.

동작 구조

1. Init Mark

Root Set을 스캔하여 참조하고 있는 객체를 찾아 마킹하는 단계

2. Concurrent Marking

애플리케이션 실행 도중에 현재 살아있는 객체에 대해 마킹하는 단계.

3. Final Mark

모든 대기열을 비우고 Root set을 다시 스캔하며 Concurrent Marking 단계를 종료합니다.
또한 복사할 빈 영역을 확인하여 초기화 시키며 다음 단계를 대기하는 단계

4. Concurrent Cleanup

garbage 영역에서 Concurrent Mark 단계 이후 더 이상 현재 살아있지 않은 객체들을 즉각적으로 회수

5. Concurrent Evacuation

개체를 다른 region으로 복사하는 과정

6. Init Update Refs

참조 업데이트 과정을 초기화하는 단계. 다음 단계를 위해 준비하는 단계

7. Concurrent Update References

Heap 을 선형으로 스캔하여 Concurrent Evacuation 동작 도중 이동된 객체들에 대해 참조를 업데이트하는 과정

8. Final Update Refs

Root Set을 업데이트 하여 객체 참조 관계를 다시 업데이트 합니다. 이 때, 마지막으로 STW가 발생

9. Concurrent Cleanup

모든 과정이 완료된 후, 참조가 없는 객체들을 회수 진행

Ellipson GC

JDK 11부터 Ellipson GC라는 No-Op Garbage Collector를 도입해 가능한 가장 낮은 GC 오버헤드를 약속하는 GC를 선보였습니다.

Ellipson GC 작동하지 않는 가비지 수집기입니다. 무슨 의미인지 모르시겠다고요??

Ellipson GC는 어플리케이션에서 사용 가능한 힙이 충분하다고 알고 있는 경우 굳이 JVM의 리소스를 사용해 GC 작업을 실행하는 것을 원치 않을 때 사용하는 GC입니다.

어플리케이션이 사용할 Heap 용량이 충분하지 않을 경우(사용 가능한 Heap 공간이 없을 경우), Ellipson GC는 지금까지 본 GC들과 달리 OutOfMemoryException을 터뜨립니다. 왜냐면 메모리 회수 코드가 구현되지 않았기 때문이죠.

따라서 해당 GC는 어플리케이션이 사용 가능한 Heap이 충분하다는 것을 아는 경우 굳이 GC를 JVM이 실행하지 않도록 하는 GC입니다.

ZGC

기존 GC들은 STW로 인해 어플리케이션의 성능에 영향을 주고 있습니다. 이러한 문제를 해결하기 위해 ZGC는 다음과 같은 목표로 태어났습니다. ZGC는 JDK 11 이후 버전부터 이용이 가능합니다.

✔ ZGC : a good fit for server applications, where large heaps are common, and fast application response times are a requirement.

  • 빠른 어플리케이션 응답속도를 요구하는 상황에서 가장 알맞는 GC입니다. 다만, 메모리가 많이 여유로운 상황에서 이용하는 것을 권장드립니다. 이유는 뒤에 설명하겠습니다.

목표
G1보다 낮은 Latency를 가지고 G1에 뒤쳐지지 않을 처리량을 가지는 GC를 개발한다.

주요 원리
colored pointers, load barriers를 함께 사용하여 GC를 위한 기능/최적화 기반을 마련합니다.

ZGC에서는 메모리를 ZPages라 불리는 영역으로 나누고 동적 사이즈로 2MB의 배수가 동적으로 생성 및 삭제될 수 있습니다.

  • (G1 GC) Region = ZPage

ZGC 전략

대부분의 GC 같은 경우, GC 가 발생시 영역이 변경되며 기존 객체가 새로운 빈 공간을 찾아 재할당 되는 과정을 거치고 있습니다. 하지만 ZGC 같은 경우는 이러한 빈 공간을 탐색하는 시간이 너무 오래 걸리기 때문에 새로운 영역을 할당해 그 곳에 객체를 이동시키는 전략을 이용합니다. 이를 Compact라 부릅니다.

하지만 이 때 기존 객체와 새로운 영역에 할당된 새로운 객체간 값의 동기화가 정상적으로 이루어지지 않을 수 있다는 문제가 존재합니다.

그림으로 보는 Compact 문제 발생

다음은 하나의 객체가 GC를 통해 재할당 되는 과정을 보여줍니다.

1. 객체 참조

2. GC로 인해 빈공간 탐색 후 할당

3. 할당 이후 기존 객체에 write

4. 할당된 객체와 기존 객체간 데이터 무결성이 깨짐

이를 보완하기 위해 ZGC는 다음과 같은 세가지 전략을 이용하고 있습니다.

ZGC에서 이용하는 전략

1. Load Barrier

JIT에 의해 Load Barrier가 주입되는데, 이를 통해 ZGC는 어플리케이션 진행과 동시에 압축을 수행할 수 있습니다. 만약 어플리케이션 쓰레드가 힙 메모리에 접근할 경우 다음 두 가지를 진행합니다.

  • 주소의 colored point가 bad color인지 체크합니다.
  • bad color일 경우 객체를 상황에 따라 mark/relocate/remapping으로 마킹합니다.

2. Reference coloring (Colored Pointers)

  • GC 메타데이터를 객체의 메모리 주소에 표시합니다.
  • GC 메타데이터는 (finalizable, remap, mark1, mark0)로 구성되어 있습니다.
  • 주의!: colored pointer의 bit가 모두 0인 경우는 없습니다
    (그림의 색칠된 부분 = GC 메타 데이터 = colored pointer)

ZGC는 이 colered pointer를 이용해 해당 color가 bad color인지 체크하고 bad color일 경우 객체를 상황에 따라 mark/relocate/remapping을 진행합니다.

3. Multi-mapping

  • reference coloring에 의해 특정 시점에서 mark0, mark1, remap 비트 중 하나는 무조건 1이 됩니다.
  • offset x에 페이지(ZPage)를 할당할 때 ZGC는 동일한 페이지를 3개의 다른 주소 영역에도 추가로 할당합니다.
  • 3가지의 다른 주소 영역은 모두 동일한 물리 메모리 주소로 설정합니다.

이제 ZGC에서 이용하는 세 가지의 새로운 개념에 대해 알아보았습니다.

그럼 ZGC는 어떻게 Compact의 문제점을 다음 세 가지 전략을 추가 개선하였을까요? 과정은 다음과 같습니다.

기존 Compact 과정 개선 방법

  1. ZGC에서는 3곳의 가상 메모리 영역에서 동일한 물리적 메모리에 엑세스 할 수 있도록 multi mapping을 진행합니다.(color mark 간 overhead를 방지하기 위해 사용)
  2. 이후, colored pointer 란 것을 이용해 heap에 있는 객체들이 참조되는지 마킹합니다.
  3. GC가 동작할 경우 colored pointer에 마킹된 값들을 통해 relocationremapping 여부를 확인하며 실제 객체와의 동기화 작업을 지속적으로 수행합니다.
  4. 모든 작업이 완료될 경우 remapping된 실제 객체를 제외한 나머지 메모리를 회수합니다.

ZGC GC 순서

ZGC의 GC 순서는 크게 2가지, 작게는 9가지로 나뉘는 순서를 가지고 있습니다.

세분화 된 ZGC 동작 순서

Pause Mark Start -> Concurrent Mark -> Pause Mark End
-> Concurrent Process Non-Strong References -> Concurrent Reset Relocation Set
-> Pause Verify -> Concurrent Select Relocation Set -> Pause Relocate Start Concurrent Reloate

크게 볼 경우 ZGC 동작 순서

  • Marking(마킹)
  • Relocating(재할당)

대충 순서도 확인했으니 이제 어떻게 동작하는지 확인해보겠습니다.

Marking

1. Mark Start

  • 모든 어플리케이션 쓰레드를 멈추고(STW) 각 쓰레드마다 가지고 있는 local variable을 스캔합니다.
  • thread local variable에서 힙으로의 참조를 GC Root 라고 하며 GC Root set을 만듭니다.

2. Concurrent Marking

  • GC Root 에서 시작해 객체 그래프를 탐색하며 도달할 수 있는 객체를 살아있다고 표시(Mark) 합니다.
  • 최종적으로 도달되지 못한 객체는 Garbage로 판단합니다.
  • 살아있는 객체들은 각 page의 livemap이라고 불리는 곳에 정보를 저장합니다.

3. Mark End

  • 모든 어플리케이션 쓰레드를 멈추고 (STW) thread-local의 marking buffer를 탐색하며 비웁니다.
  • 아직 marking 하지 않은 참조 객체들 중 큰 하위 객체 그래프가 존재할 경우 응답시간이 오래 걸릴 수 있기 때문에 1ms 후에 mark end 단계를 끝내고 다시 2.Concurrent Marking 단계로 돌아간 다음 다시 Mark End 단계를 반복합니다. (전체 그래프 탐색할 때 까지)

그림으로 보는 Marking 과정

Relocating

1. Concurrent Processing

GC Thread

  • Prepare Relocation Set
    • Relocation Set은 page 들의 Set으로 forwarding table 을 할당합니다.
    • forwarding table은 기본적으로 객체가 재배치된 주소를 저장하는 hash map입니다.
  • Concurrent Destroy Detached Pages
    • 조만간 사라질 가비지로 가득찬 page들
  • Prepare Relocation Set을 대상으로 Relocating을 진행하기 위해 준비합니다.

Application Thread

  • 계속해서 어플리케이션 서비스 작업을 수행합니다.
  • 작업중 만나는 도달하지 못한 객체를 만날 경우, load barrier로 인해 마킹을 진행하고 GC와 동일하게 메모리 재할당을 시킵니다.

2. Relocation Start

  • 모든 어플리케이션을 멈추고(STW) relocation set의 page에 있는 객체 중 GC Root에서 참조되는 모든 객체들을 모두 Relocation/Remapping 합니다.

3. Concurrent Relocation

  • GC 및 어플리케이션 쓰레드는 살아있는 객체를 탐색하여 새로운 ZPage로 재할당을 동시에 진행합니다.
  • 재배치는 먼저 탐색하여 재할당을 진행하는 쓰레드가 우선순위를 가집니다.
  • 최종적으로 GC 쓰레드가 relocation set에 있는 모든 객체를 relocate하여 할당한 부분을 확인하는 것까지 마치는 즉시 완료됩니다.

4. Concurrent Remapping

  • 재할당이 끝났으니 기존 Old 객체들의 참조 정보를 재할당 된 새로운 객체로 참조를 변경합니다.
  • 다음 GC 사이클 이전에 모두 완료되지 않을 수 있습니다.

그림으로 보는 Relocating

1. Relocation Set

2. GC thread Relocating

3. Application thread accessing Object

4. GC thread Relocating E

5. 우선순위로 인한 GC thread relocating 실패

6. GC thread Relocating F(Finish)

7. Remapping pointer

성능 비교

JDK 버전별 각 GC의 성능을 비교한 자료입니다. 자세한 내용은 GC progress from JDK 8 to JDK 17 에서 참고하면 좋을 것 같습니다. 현재 버전별 default GC는 다음과 같습니다.

JDK 버전별 GC

  • JDK 8: Parallel GC
  • JDK 11: G1 GC (CPU 1개일 경우 Serial GC)
  • JDK 17: G1 GC (CPU 1개일 경우 Serial GC)

처리량 비교

지연 시간

일시 중지 시간

메모리 overhead

성능비교 종합

  • 버전이 업그레이드 될수록 GC의 성능이 개선되는 것을 확인할 수 있습니다.
  • 시스템의 일시 중지 시간을 크게 고려할 경우 ZGC GC 선택을 고려해봐야 합니다.
  • 메모리 관리 측면에서는 G1 GC보다는 뒤떨어지는 단점이 존재합니다.
  • 종합적으로 보았을 때 G1 GC의 성능이 가장 뛰어납니다.

결론

다양한 GC가 존재하지만 현재 보편적으로 사용되는 jdk 8은 Parallel GC를 default로, jdk 11은 G1 GC를 default로 사용하고 있습니다.

이러한 점을 고려해 버전 업그레이드시 G1 GC에 대한 이해도를 보다 더 높여야 겠다는 생각을 가지게 되었습니다.

G1 GC에 대해 좀 더 알아보고 싶은 분들은 다음 링크를 추가로 확인하면 좋을 것 같습니다.

하지만 다음과 같은 상황에서는 해당 GC 도입을 고려해볼만 하다고 생각합니다.

상황별 GC 선택

  1. 어플리케이션 응답시간을 최소화 하고 싶은 경우
  • Shenandoah GC
  • ZGC
  1. GC가 필요없을 정도로 메모리 영역이 클 경우, 어플리케이션 OOME 발생 유무를 테스트하고 싶은 경우
  • Ellipson GC
  1. Single Core일 경우
  • Serial GC

Reference

JEP 318: Epsilon: A No-Op Garbage Collector - Experimental

No-Op Experimental Garbage Collector

JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector

ZGC에 대해서

ZGC

profile
Gelog 나쁜 것만 드려요~

0개의 댓글