(WIP) GC 알고리즘
GC 알고리즘
GC 알고리즘 종류
GC 알고리즘은 Java 언어의 성능과 직결되기 때문에, Java 언어와 함께 꾸준히 개선되어오고 있다.
Serial GC
Parallel GC (JDK 7)
CMS GC(JDK 14, not available currently)G1 GC (JDK 9)
Shenandoah GC (OpenJDK only)
ZGC
Epsilon GC
메모리 수집을 하지 않기 때문에 다른 GC와 결이 다르다.
GC Phase properties
a parallel phase
멀티 스레드로 실행 가능
a serial phase
싱글 스레드로 실행 가능
a stop the world phase
프로그램과 동시에 실행될 수 없으므로 프로그램이 일시적으로 멈춘다.
a concurrent phase
백그라운드에서 실행되어 앱 작업과 동시에 실행될 수 있다.
an incremental phase
작업 완료 전에 종료하고 나중에 이어서 작업할 수 있다.
Serial GC
싱글 스레드로 동작하는 가장 간단한 GC이다.
GC가 실행되면 모든 스레드가 STW 되어버리므로 서버 환경에 적합하지 않다.
쉽게 말하면 GC가 돌 때 프로그램 자체가 잠시 멈춰버리므로 요청을 제때 처리할 수 없게 된다!
클라이언트 앱 또는 일시 중지 시간에 대한 제한이 없는 경우에 선택하는 GC 방식이다.
다음 옵션을 주어 활성화 시킬 수 있다.
java -XX:+UseSerialGC -jar <Application.java>
Parallel GC
힙 영역 관리를 위해 멀티 스레드를 활용하는 GC이다.
Minor GC
를 처리하는 스레드를 병렬로 처리하여 Serial GC보다 훨씬 빠르게 동작한다.Serial GC 보다 STW 시간을 단축할 수 있다는 장점이 있다.
GC 수행 시에는 Serial GC와 동일하게 다른 앱 스레드도 정지되는 STW 이벤트이다.
다음 옵션을 주어 활성화 시킬 수 있다.
java -XX:+UseParallelGC -jar <Application.java>
다음과 같은 파라미터를 지정할 수 있다.
최대 GC 스레드 수:
-XX:ParallelGCThreas=<Number>
최대 일시 정지 시간:
-XX:MaxGCPauseMillis=<Number>
최대 목표 처리량:
-XX:GCTimeRatio=<Number>
처리량은 GC에 소요된 시간과 GC 수집 시 외부에서 소요된 시간(응용 프로그램 시간) 측면에서 측정하게 된다. 즉, (GC 시간) / (응용 프로그램 시간) = 1 / (1+n) 이 되도록 하는 n의 값을 지정할 수 있다. 예를 들어 n을 19라고 설정하면, GC에 소요되는 시간을 5% 이내가 되도록 한다.
최대 힙 크기:
-Xmx<Number>
CMS GC
Concurrent Mark Sweep 의 줄임말로 멀티 GC 스레드를 활용하는 GC이다.
STW로 인해 응답하지 못하는 시간이 길어지지 않도록 하는 것이 이 GC 방식의 목표이기 때문에 애플리케이션 실행을 멈추지 않고 진행 가능한 Concurrent Mark, Concurrent Sweep 작업과 STW가 꼭 필요한 Initial Mark, Remark 작업으로 단계를 나누어 STW를 줄였다.
하지만 다른 GC 방식보다 메모리와 CPU를 더 많이 사용하고, Compaction 단계가 기본적으로 제공되지 않는 단점이 존재하였고, 결국 G1GC에 의해 대체되어 JDK 14에서 아예 제거되었다.
수행 과정
Minor GC
Live 객체는 Eden과 Survivor 영역에서 다른 Survivor 영역으로 이동하다가, 특정 임계값을 넘어서면 Old 영역으로 이동한다.
Full GC
Old 영역의 점유율이 기준치에 도달하면 CMS 작업이 시작된다.
작업은 아래와 같이 5단계로 나뉜다.
Initial Mark (STW)
GC root를 찾는 작업 등에 의해 Initial Mark는 STW가 발생한다.
클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다.
GC 루트로부터 도달 가능한 객체를 마킹 (Old 제너레이션 내에 객체는 도달 가능한 것으로 간주됨)
일반적인 마이너 GC의 STW 시간에 비해 짧다.
Concurrent Marking
다른 스레드가 실행 중인 상태와 동시에 마킹 처리를 한다.
Initial Mark 작업으로 살아있음을 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다.
Remark (STW)
Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한 후 사용되지 않는 객체는 마킹해준다.
Concurrent sweep
다른 스레드가 실행 중인 상태와 동시에 객체들을 sweep(제거)하여 메모리를 확보한다.
Resetting
다음 GC 작업이 원활히 수행될 수 있도록 GC threshold 등을 초기화한다.
G1 GC
G1(Garbage First)은 메모리 공간이 큰 멀티 프로세스 장비에서 실행되는 프로그램을 위해 설계된 GC이다.
CMS보다 성능이 좋고 효율적이어서 CMS를 대체하게 되었다.
힙 메모리 영역을 동일한 크기의 region으로 분할하여 관리한다.
Region
G1 GC에서 힙은 하나의 메모리 영역이다. 즉, 다른 GC가 eden, survivor, old 영역을 가지는 것과 다르게 하나의 메모리 영역만을 갖도록 한다. 아래 사진을 보면 바로 이해할 수 있을 것이다.
G1은 위와 같이 하나의 메모리 영역(area)을 가지지만 이 안에서 여러 region으로 나뉜다.
각 영역들은 가상 메모리의 연속 범위에 할당된다.
힙은 약 2000개 영역으로 분할되며 영역의 크기는 JVM이 시작될 때 자동으로 결정되며 1MB~32MB 사이이다.
Region은 Eden, Survivor 및 Old Generation 영역으로 할당될 수 있다.
live 객체는 다른 영역으로 복사 또는 이동된다.
Humongous Region은 표준 영역 크기의 50% 이상인 객체가 있다면 이를 보관하는 공간이다. 인접 Region들의 집합으로 구성된다.
Young GC
Young Generation (Eden, Survivor) 에서 발생하는 GC이다.
여러 스레드에서 병렬으로 실행되며, STW가 발생한다.
Young Generation 메모리는 연속적이지 않은 Region의 집합으로 구성되므로, 크기를 쉽게 조정할 수 있다.
Live 객체는 Survivor Region 사이를 이동하며 Aging Threshold가 충족되면 Old Generation Region으로 이동한다.
Eden과 Survivor의 크기는 다음 Young GC를 위해 계산하며, 크기를 측정할 때 도움울 준다.
다음 이미지를 보면, 참조되지 않는 객체는 제거하고 Live 객체만 모아 새로운 Young Generation Region으로 이동하여 Young GC를 마친다.
Full GC
Initial Marking (STW)
Old Region의 객체에 대한 참조가 있는 Survivor Region(Root Region)이 있는지 확인하여 마킹한다.
live 객체 마킹은 Young GC 작업과 결합하여 수행되며, 로그에
GC pause(young)(initialmark)
라고 표기된다.
Root Region Scan
Initial Marking에서 찾은 Survivor Region에 대한 GC 대상 객체 스캔 작업을 진행한다.
애플리케이션이 실행되는 동안 발생되며, Young GC가 발생할 가능성이 있기 전에 작업이 완료되어야 한다.
Concurrent Mark
애플리케이션이 실행되는 동안 전체 힙에서 live 객체를 찾는다.
Young GC가 발생하면 중단될 수 있다.
빈 region이 발견되면 다음 단계인 Remark 단계에서 즉시 제거되며, 활성을 결정하는 정보가 계산된다.
Liveness 정보는 앱이 실행되는 동안 내내 계산되며, liveness를 통해 GC 수집 중 가장 좋은 리전을 선별한다.
CMS와 같은 스위핑 단계가 없다.
Remark (STW)
애플리케이션을 잠시 멈추고 최종적으로 GC 대상에서 제외되는 live 객체를 마킹한다.
CMS 수집기에서 사용된 것보다 훨씬 빠른 SATB(snapshot-at-the-beginning)라는 알고리즘을 사용한다.
STW가 일어난 직후의 스냅샷을 사용해 live 객체에 마킹하므로, Remark 작업 중에 죽은 객체도 live 객체로 간주된다.
완전히 빈 Region은 제거하고, 모든 Region을 위해 Liveness가 계산된다.
Cleanup (STW & Concurrent)
live 객체와 완전히 비어있는 region에 대해 계산한다.
live 객체가 가장 적은 region에 대한 미사용 객체 제거를 수행한다.
비어있는 region을 초기화하고, 빈 region 정보를 Freelist 리스트에 추가해 다시 사용할 수 있도록 한다.
Copying (STW)
GC 대상 Region이었지만 Cleanup 과정에서 살아남은 Region의 객체들을 새(Available/Unused) Region 에 복사하여 Compaction 작업을 수행한다.
앞서 말한 살아남은 region을 수집하는 과정은 Young GC와 동시에 이뤄지며 로그에 [GC pause (mixed)]로 표기된다. 즉, Young, Old 제너레이션에 대한 작업이 동시에 이뤄진다.
liveness가 가장 낮아 빨리 수집 가능한 region을 선택해 수집한다.
용어
Remembered Sets(RSet)
reference를 가진 Object들이 어느 region에 있는지 알기 위해 사용하는 자료구조
주어진 region에 대한 참조 객체를 추적한다. 각 region 당 하나의 RSet이 존재하며 이를 통해 Region의 병렬 및 독립된 수집을 가능하게 한다. RSet이 전체 영역에서 차지하는 비율은 5% 이내이다.
GC 로그에서 이 Reference 정보를 갱신(update)하고 검색하는데 소요되는 시간을 확인 할 수 있다.
Baker's Incremental GC 알고리즘, Baker's Incremental Copying Collector 알고리즘 을 참고하자. Reference를 가진 Object를 찾아 라이브 객체를 복사하는 알고리즘이다.
Collection Sets(CSet)
GC가 수행될 Region 집합
CSet 내의 데이터는 GC 동안 모두 비워진다(복사되거나 이동됨).
Region 집합은 Eden, Survivor, Old Generation으로 이루어질 수 있다.
CSet이 JVM에서 차지하는 비율은 1% 이내이다.
GC 로그에서 CSet을 선택하고 Processing 후 CSet을 해제하는데 걸린 시간을 확인할 수 있다.
Evacuation Pauses와 Mixed GC를 수행할 때 사용하는 자료구조
Shenandoah GC
OpenJDK에서만 존재하는 GC
STW 시간을 최소화하는 것을 목표로 하며, 짧은 STW 시간을 위해 메모리 사용을 최대한 활용한다.
G1과 같이 힙 영역이 리전이라는 가상의 공간으로 나눠지지만 G1을 비롯한 GC들과 달리 힙을 제너레이션으로 나누지 않는다.
앱과 동시에 더 많은 GC 작업을 수행해 일시 중지 시간을 줄인 GC (CMS와 G1 처럼)
동시 압축 기능이 추가되어 힙 크기와 상관없이 일관된 일시 중지 시간을 가진다.
응답 시간이 짧고 GC STW가 예측이 가능할 경우 유효한 GC 알고리즘
브룩스 포인터를 통해 참조 재배치 메커니즘을 처리한다.
브룩스 포인터(Brooks Pointer): 각 객체의 실제 위치를 가리키는 필드
실행 단계에서 참조를 변경하기 때문에 브룩스 pointer를 두어 메모리 위치를 이동시켜도 참조가 유지되도록 한다.
앱을 중단하지 않고 compaction 진행
G1에 비해 앱의 실행이 중단되지 않도록 설계됨
수행 과정
Initial Mark (STW)
Concurrent Marking
앱 실행과 동시에 힙 내에 객체를 추적하는 단계
이 작업의 시간은 Live 객체의 수와 객체 그래프에 따라 달라짐
또한 앱은 이 단계에 새 데이터(객체)를 할당 가능 (이 작업이 앱의 실행을 중단시키지 않음)
Final Mark (STW)
잠시 중단되었던 마킹/업데이트 큐를 비우고 루트 셋을 다시 스캔하여 동시 마킹 작업을 완료하는 단계
완료 후 비워져야 할 리전을 파악, 다음 단계를 위해 비우고 초기화 등 작업을 수행
대기열을 비우고 루트 셋을 비우는 작업에 따라 STW 시간은 크게 달라짐
Concurrent Cleanup
즉각적인 GC 영역으로 동시 마킹 작업 후 Live 객체가 없는 리전을 회수하는 단계
Concurrent Evacuation
컬렉션 셋을 다른 리전으로
동시에
이동(복사)하는 단계 (이것이 다른 GC와의 주요 차이점)이 단계는 앱과 함께 다시 실행되어 여유 공간인 리전을 할당할 수 있음
선택한 컬렉션 셋의 크기에 따라 작업 시간은 크게 달라짐
Init Update Refs (STW 중 제일 짧음)
이동한 객체들의 참조 업데이트를 초기화하고, 모든 GC와 앱 스레드가 제거 작업을 완료했는지 확인하며 다음 단계를 위해 GC를 준비하는 단계 (이외에는 거의 아무것도 하지 않음)
즉 이동 및 제거 등 작업이 완료된지 확인한 후에 다음 단계를 위해이동한 객체에 대한 포인터 등을 업데이트 작업 등을 초기화(준비)
Concurrent Update References
힙을 살펴보고,
Concurrent Evacuation
작업 중 이동된 객체에 대한 참조를 업데이트하는 단계다른 GC와의 주요 차이점이다.
해당 작업은 앱의 실행과 동시에 진행되며 힙의 객체 수에 따라 작업 시간이 달라짐 (하지만 객체 그래프에 영향을 받지는 않음)
Final Update Refs (STW)
기존에 루트 셋을 업데이트하여 참조 업데이트 단계를 완료하는 단계
컬렉션 셋에서 리전을 재활용하며 루트 셋의 크기에 따라 STW 시간은 달라짐
Concurrent Cleanup
참조가 없는 컬렉션 셋 리전을 회수
ZGC
힙 크기에 영향을 받지 않는다
참조의 메타데이터 비트 확인, 참조 얻기 전 일부 처리 수행
64 bit 환경에서만 사용 가능
동작 과정
marking
Reference Coloring
참조 컬러 - 4개의 메타데이터 비트 정보로 remapped 되었는지 확인 가능
Relocation
Remapping & Load Barriers
로드 배리어를 사용해 힙에서 객체 참조를 로딩할 때마다 실행되도록 한다.
로드배리어가 Remapping을 수행해준다.
Epsilon GC
메모리를 할당하지만 실제로 회수하지 않는 GC이다.
사용 가능한 메모리를 모두 사용하면 프로그램이 종료된다.
메모리 풋프린트와 처리량을 낮추면서 대기시간이 가장 낮은 형태로 최소한의 작업만 수행하는 데 초점을 맞추고 있다.
GC의 영향을 피하면서 성능이나 메모리 부하 테스팅하고 싶을 때 사용할 수 있다.
Last updated