GC의 기본
GC는 '대부분의 객체는 생성되고 곧 쓰레기(Unreachable Object)가 된다'라는 가설을 전제로 만들어졌다.
GC는 모든 방법을 막론하고, 단계별로 minor GC, major GC, full GC로 나누어 조건에 따라 GC를 실행한다.
일반적으로 minor GC는 Young Generation를 대상으로 하고, major GC는 Old Generation을 대상으로 하여 GC를 실행한다. full GC는 STW(stop-the-world)로 실행중인 애플리케이션을 멈춘 뒤 모든 메모리 영역에 대해 GC를 실행한다.
GC의 방법에 따라 low latency 또는 high throughput를 추구하는 방향이 있다.
reference counting은 생각보다 오버헤드가 많다고 한다. 예를 들면 multi thread 환경일때 counting을 CAS operation을 사용해야 한다던가, 매 참조마다 카운팅 연산이 들어가기 때문이다. 그래서 사용하지 않는듯 하다.
기존 GC (G1 GC 등장 이전)
- heap을 Eden, Survivor 0, Survivor 1, Old region으로 나눈다.
- Eden과 Survivor 0, Survivor 1을 young generation이라고 하는데, Survivor 둘 중 하나에만 객체가 존재한다.
- minor GC는 Eden과 사용중인 Survivor의 살아있는 객체들을 찾아서 사용중이지 않은 Survivor에 넣는 작업이다.
- Survivor에서 threshold만큼 살아서 옮겨다닌 객체는 Old로 옮겨진다.
- major GC는 mark and sweep 방식으로 Old region을 정리한다.
GC 방법 | minor GC | major GC | 목표 |
---|---|---|---|
Serial GC | serial | serial mark - sweep - compaction | 싱글 프로세서 |
Parallel GC | parallel | parallel mark - sweep - compaction | high throughput |
CMS GC | parallel | concurrent mark - sweep | low latency |
Serial Garbage Collector
- 메모리 사용량이 100MB 이하이고, 싱글 프로세서일때 사용한다. 모든 GC 과정이 STW를 수반한다.
- tenured generation이 꽉 차면 major GC가 실행된다.
Parallel Garbage Collector
- Serial GC의 minor, major GC를 parallel로 진행한다. 모든 GC 과정이 STW를 수반한다.
- maximum pause time goal을 설정할 수 있다. default는 제한 없이 멈출 수 있어서 throughput이 가장 높지만, 이 값을 설정하면 그 이하로 줄이기 위해 collector가 GC와 관련된 파라미터 값을 조정한다.
- throughput goal을 설정할 수 있다. default는 1%의 시간만 GC에 사용하도록 한다.
- minimum footprint(프로그램이 실행되는 동안 사용하는 heap size) goal을 설정할 수 있다. 위 두 goal이 만족될 때, 이 goal도 고려해서 GC한다.
CMS Garbage Collector
- major GC 전체 시간동안 STW를 하지 않고, 두 번의 작은 pause time만으로 수행한다. pause time을 최소화하고, reachable object trace와 sweep을 애플리케이션과 동시에 수행해서 latency를 줄인다.
- initial mark (root 바로 근처의 object를 mark, STW)
- concurrent mark (1에서 mark된 object가 참조하는 object들을 따라가서 확인)
- remark (새로 추가되거나 참조가 끊긴 객체를 확인, STW)
- concurrent sweep (쓰레기 정리)
- 최근 히스토리에 따라 major GC를 수행해야할 시점을 예상하고 수행하는 방법(automatic pacing)도 있고, Old 영역이 일정 퍼센트 이상 차면 major GC를 수행하는 방법도 있다.
- 사용 CPU, 메모리가 많고, compaction 과정이 기본적으로 없기 때문에 fragmentation이 일어난다. 그래서 compaction이 실행되면 다른 GC에 비해 STW 시간이 오래 걸린다.
G1 GC
CMS를 장기적으로 대체하기 위해 만들어졌다. CMS보다 GC pause를 더 짧게 가져가도록 만들어졌고, CMS보다 튜닝하기 쉽게 만들어졌다. default 설정은 high throughput과 low latency를 적절한 밸런스로 고려해서 수행한다.
또한, CMS를 튜닝하기 위해서는 여러 가지 옵션을 설정해야 한다. 또한, Old generation 기준으로 occupancy를 판단하는데, G1 GC는 전체 heap 기준으로 보기 때문에 더 직관적이다. 그 외에도, G1의 목표도 high throughput 또는 low latency 쪽으로 치우쳐서 goal을 만들 수도 있다. 다른 특징으로는 CMS보다도 메모리를 조금 더 쓴다는 점이 있다. Remember Set, Collection Set을 region마다 사용하기 때문이다.
G1 GC는 heap을 일정 크기의 block으로 나누고, 각 block마다 Eden, Survivor, Old, Humongous, Not allocated space 중 하나를 맡는다. 관리 방법이 달라지므로, minor GC, major GC 방법도 당연히 달라진다.
- minor GC
- evacuation (STW)
- 통계 수집 (다음 minor GC를 언제 하는지에 따라 Eden, Survivor가 가져야할 크기를 계산)
- 계산대로 Eden, Survivor의 block 개수를 resize
- major GC
- initial mark (minor GC의 evacuation과 같이 수행하기 때문에 STW가 필요없음)
- concurrent mark 및 liveness 계산
- remark (STW)
- 낮은 liveness를 가진 block부터 evacuation 수행(evacuation하는 동안 STW).
- 대표적인 option
- MaxGCPauseMillis (목표로 하는 최대 pause time)
- InitiatingHeapOccupancyPercent (GC를 시작할 heap occupancy 지정)
- NewRatio, SurvivorRatio (명시적으로 young generation 크기를 지정함. 이 경우 통계 수집에 따라 resize 하지 않음)
- GCTimeRatio (default 9, = 90%를 애플리케이션에 쓰고 10%를 GC에 사용해도 된다)
참고
[1]https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html
[2]https://d2.naver.com/helloworld/1329
[3]www.youtube.com/watch?v=bhVzCIk3-Q4&ab_channel=OracleLearning
[4]johngrib.github.io/wiki/jvm-memory/#garbage-first-garbage-collector