본문 바로가기
백엔드

[Java] 2. Garbage Collection - GC의 과정 및 종류

by BeforB 2022. 6. 24.
728x90

 

 

1. Garbage Collection 과정

모든 GC 알고리즘은 Mark & Sweep 2가지 기본 작업을 수행한다.

 

Step 1. Mark

가비지 컬렉터가 어떤 객체가 사용되고 어떤 객체가 사용되지 않는지 식별하는 과정

객체가 생성되면 객체의 mark bit를 0으로 세팅한다. Mark 단계에서는 모든 reachable 객체에 bit를 1(true)로 마킹한다. 마킹 작업은 단순 그래프 탐색을 요구하기 때문에 깊이우선 탐색을 이용한다.

root는 객체를 참조하는 지역변수로, root가 여러 개일 경우 모든 root들에 대해 Mark()를 수행한다.

객체가 처음 생성되면 객체의 mark bit를 0으로 세팅하고 Mark 단계에서 모든 reachable 객체에 bit를 1(true)로 마킹한다. 마킹 작업은 단순 그래프 탐색을 요구하기 때문에 깊이우선 탐색을 이용한다.

root는 객체를 참조하는 지역변수로, root가 여러 개일 경우 모든 root들에 대해 Mark()를 수행한다.

 

 

 

 

 

 

 

2. Garbage Collection의 종류

1) Serial GC

Serial GC는 CPU 코어가 하나만 있을 경우 사용하기 위해 만든 방식으로 Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어진다.

Serial GC의 경우 Young Generation에서의 GC는 Mark-Sweep 알고리즘을 사용하고 Old Generation에서는 Compact 과정이 추가된 Mark-Sweep-Compact 알고리즘을 사용한다.

 

 

 

2) Parallel GC

Parallel GC는 Throughput GC라고도 부르며 Serial GC와 기본적인 알고리즘은 같지만 Serial GC는 쓰레드가 하나인 반면 Parallel GC는 쓰레드가 여러 개이므로 Serial GC보다 빠른 성능을 보장한다. 메모리가 충분하고 코어의 수가 많을 때 유리하다.

Serial GC와 Parallel GC 비교 - 이미지 출처: “Java Performance”, p.84

 

 

3) Parallel Old GC(Parallel Compacting GC)

JDK 5 update 6부터 제공한 GC 방식으로 Java8까지 Default GC로 사용되었다. Parallel GC와 Young Generation 알고리즘은 동일하나 Old Generation 알고리즘이 다르다.

Mark-Summary-Compaction 단계를 거치며 Summary 단계에서는 앞서 GC를 수행한 영역에 대해 별도 살아있는 객체를 식별한다는 점에서 Sweep보다 좀 더 복잡한 알고리즘이 수행된다.

 

 

 

4) Concurrent Mark & Sweep GC(CMS)

Initial Mark - Concurrent Mark - Remark - Concurrent Sweep 단계를 거친다.

 

 

1) Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체를 찾으며 멈추는 시간이 매우 짧다.

2) Concurrent Mark 단계는 STW가 발생하지 않고 다른 쓰레드들이 실행되고 있는 상황에서 진행된다. 이 단계에서는 Initial Mark 단계에서 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 reachable 객체들을 확인한다.

3) Remark 단계는 이전 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.

4) Concurrent Sweep 단계에서도 다른 쓰레드들이 실행되고 있는 상황에서 진행되며, 가비지를 정리하는 작업을 수행한다.

 

 

장점 - 위의 방식대로 진행하기 때문에 CMS GC는 STW 시간이 매우 짧기 때문에 Low Latency GC라고도 부르며, 응답속도가 매우 중요할 때 CMS GC를 사용한다.

단점 - 다른 GC보다 메모리와 CPU를 더 많이 사용하며 Compaction 단계가 기본적으로 제공되지 않는다. 만일 Compaction GC를 실행할 경우 다른 GC보다 STW가 더 길어지기 때문에 Compaction작업이 얼마나 빈번하고 오랫동안 수행되는지 확인해야 한다.

 

 

 

 

5) G1(Garbage First) GC

기존의 GC들과는 전혀 다른 새로운 형태의 GC이다. 자바에서는 JDK 7부터 G1 GC가 지원되며 JDK 9에서 기본 가비지 컬렉션으로 지정되었다. G1 GC는 다른 GC들과 달리 Young / Old 영역들을 물리적으로 나뉘어진 공간을 사용하지 않고 일정한 크기의 Region이라는 개념을 도입하여 Heap을 균등하게(평균적으로 1MB-32MB 사이) 나누고 각 Region을 논리적으로 구분하여 객체를 할당한다.

 

 

G1 GC에서는 Eden, Survivor, Old, Humongous, Available/Unused라는 다섯가지 Region을 사용한다. Humongous 영역은 REgion크기의 50%를 초과하는 객체를 저장하기 위한 공간이며 Available/Unused는 사용되지 않고 비어있는 Region이다.

G1 GC의 핵심은 Heap을 동일한 크기의 Region으로 나누고 가비지가 가장 많은 Region에 대해 우선적으로 GC를 수행한다. 이 때문에 Garbage First(G1) Collector라고 부른다.

 

 

특징

-XX:MaxGCPauseMilis=n을 설정하여 최대 정지 시간을 제한할 수 있으며, G1 GC는 최대 시간동안 수집할 수 있는 최대 region 수를 추정하여 정해진 시간을 거의 초과하지 않는 시간 내에 가비지 수집을 마친다.

G1 GC는 GC가 시작되는 초기단계에만 응용 프로그램을 중지하고, 가비지 수집은 다른 쓰레드들과 동시에 수행된다.

Heap 영역이 클수록 잘 동작하며, 작을 경우에는 공간 부족 위험이 높아지고 Full GC가 발생하게 된다. Full GC는 Single Thread로 동작하기 때문에 애플리케이션의 성능을 저하시킨다.

 

G1 GC 과정

Young-Only 단계와 Space Reclamation 단계가 반복된다.

 

1) Young-Only Phase

  • Concurrent Start
  • Young Generation의 GC로 Young GC를 수행하면서 Old Region에서의 Marking이 동시에 진행된다. Marking은 Remark와 Cleanup 단계에서 STW를 발생시킬 수 있다.
  • Remark
  • Marking을 마무리하고 Empty Region을 회수하고 내부 데이터 구조를 정리한다. Remark와 Cleanup 사이에서 G1은 Old Region에서 여유 공간을 회수할 수 있도록 계산한다.
  • CleanupSpace-Reclamation(공간 재확보) 단계가 온다면 Young-only 단계는 1회의 GC만 진행하고 완료된다.
  • Region 회수가 실제로 진행될지 여부를 결정한다.

2) Space Reclamation

  • Old Region의 가비지들을 비우는 여러 번의 Mixed-GC로 구성되어 있다. G1이 Old Region을 더이상 효율적으로 줄이기 어렵다고 판단되면 종료된다.

 

 

 

6) ZGC

JDK 15버전에서 Production Ready 상태로 제공되었다. ZGC의 핵심은 객체를 가리키는 포인터를 이용하여 객체를 Marking하는 것이다.

G1 GC의 단점을 보완하여 조금 더 큰 메모리(8MB~16TB)에서 효율적으로 GC하기 위한 알고리즘이다. 소개에 따르면 STW를 없애지는 않지만 시간을 최대한 적게 하는 알고리즘이다.

 

ZGC는 더이상 STW를 완벽하게 제거하지 않는다. ZGC를 이용할 경우 marking 시작이나 marking을 끝낼 때, 재할당을 시작할 때 애플리케이션이 중지되지만 중지시간은 매우 작은 시간(10ms 이하) 내에 이루어진다.

 

 

G1 GC와 비교하였을 때 Region이 매우 단순한 구조로 설정되어 있다. ZGC의 핵심은 Colored pointersLoad barriers 알고리즘이다.

 

 

* Colored pointers

  • ZGC는 객체를 가리키는 변수 포인터에서 64bit를 활용하여 Marking을 한다. 따라서 64bit 운영체제에서만 사용이 가능한 알고리즘이다.

ZGC

  • Finalizable : finalizer을 통해서만 참조되는 Object의 Garbage
  • Remapped : 재배치 여부를 판단하는 Mark
  • Marked 1 / 0 : Live Object

 

 

* Load Barriers단계

  1. Mark Start STW : ZGC의 Root에서 가리키는 객체 Marking
  2. Concurrent Mark/Remap : 객체의 참조를 탐색하며 모든 객체에 Marking
  3. Mark End STW : 새롭게 들어온 객체들에 대해 Marking
  4. Concurrent Pereare for Relocate : 재배치하려는 region을 찾아 Relocation Set에 배치
  5. Relocate Start STW : 모든 Root 참조의 재배치를 진행하고 업데이트
  6. Concurrent Relocate : 이후 Load Barriers를 사용하여 모든 객체를 재배치 및 참조 수정
  • G1 GC와 다르게 메모리를 재배치하는 과정에 STW 없이 재배치를 한다. 이 때 RemapMark와 RellocationSet을 확인하며 reference와 mark를 업데이트한다.

 

성능 비교

  • 최악의 경우 G1 GC와 비교하여 1000배에 가까운 시간 차이가 나는 것을 볼 수 있다.

Heap Size 128G, CPU Intel Zeon E5-2690 2.9GHz, 16 core 환경에서 측정

 

 

 

 

출처

https://thinkground.studio/일반적인-gc-내용과-g1gc-garbage-first-garbage-collector-내용/

https://syhwang.tistory.com/89

https://huisam.tistory.com/entry/jvmgc

 

 

 

728x90

댓글