Garbage Collection
by Jaesang Lim
Gabage Collection
Heap 구조, GC의 기본동작, 오라클 JCM에서 지원하는 GC 알고리즘에 대해 알아보자 ~
1. Oracle 문서에 권하는 기본 GC 튜닝 전략
- swap 메모리 사용을 피함
- 기본값보다 더 큰 힙이 필요하지 않는 이상 힙 크기를 변경하지 않음
- 단, 물리 메모리보다 더 크게 힙을 설정하면 안됨
-
튜닝이 필요하다면 Behavior based tuning을 시도
- 두 가지 옵션을 지정하여 자동으로 GC를 튜닝하는 방법
- 최대 중지 시간 목표 (Max pause time goal)
- GC가 프로그램 쓰레드를 멈추는 시간의 최대값
- 성능 목표 (Throughput goal)
- 프로그램 쓰레드 대비 GC가 실행되는 시간의 비율
- 메모리 사용량 최적화 (Footprint goal)
- 위 두 옵션을 만족했을 때 자동으로 메모리 사용량을 줄여나가는 기능
- 따로 설정하지 않음
- 최대 중지 시간 목표 (Max pause time goal)
- 최대 중지 시간 목표 (Max pause time goal)
- -XX:MaxGCPauseMillis=
- 기본값은 무제한
- 성능 목표보다 우선 순위 높음
- 목표 달성이 어렵다면 힙 크기를 조금씩 줄여나가면서 목표를 이루려고 시도함
- 대신 힙이 금방 가득 차면서 잦은 GC 호출로 인해 성능이 저하될 수 있음
- -XX:MaxGCPauseMillis=
- 성능 목표 (Throughput goal)
- -XX:GCTimeRatio=
- 값을 그대로 사용하지 않고 수식으로 계산된 비율만큼 전체 실행 시간에서 GC를 실행함
- 수식: 1 / (1 + NNN)
- 예: NNN=19 일 때는 1 / (1 + 19) = 0.05 (5%)
- 기본값은 알고리즘에 따라 다름
- Throughput Collector는 1%
- CMS는 5%
- G1은 8%
- 이 목표를 달성하지 못하면 힙 크기를 조금씩 증가시키면서 GC가 일어나는 간격을 늘려서 목표를 달성하려고 시도함
- 중지 시간이 늘어날 수 있음
- -XX:GCTimeRatio=
- 튜닝 결과 확인
- 성능 목표는 달성했지만 중지 시간이 길다면 최대 중지 시간 목표를 조정
- 최대 중지 시간 목표와 성능 목표는 트레이드오프(trade-off)이므로 최적값을 찾아야 함
- 기본 설정된 힙을 최대로 사용해도 목표를 달성하지 못했다면 힙 크기 조정
- 필요하다면 힙 크기 외에 Generation별 크기를 조정할 필요가 있음
- 목표를 달성하지 못했다면 사용 중인 서버에서 이룰 수 없는 목표는 아닌지 확인
- 필요한 경우 좀 더 복잡한 튜닝 시도
- 성능 목표는 달성했지만 중지 시간이 길다면 최대 중지 시간 목표를 조정
2. GC에 대한 이해
- 힙 구조
- GC 기본 동작 과정
- GC 알고리즘별 특징과 차이점
2_1. 힙은 여러 Generation을 나눠져있음
2_2. 힙이 나눠진 이유는 ?
- Weak Generational Hypothesis
- 대부분의 객체는 아주 짧은 시간동안 사용됨
- 소수의 객체는 오랜 시간 사용됨
- GC 성능은 사용 중인 객체의 수에 크게 영향을 받음
- Generation 구분이 없다면 빈번하게 만들어지고 사라지는 객체를 처리하기 위해 오랫동안 살아있는 객체들을 매번 확인해야함
2_3. Young Generation
- Eden
- (대부분의) 객체가 생성되는 곳
- Survivor Space
- Eden에서 살아남은 객체가 잠시동안 머무는 곳
- 두 개의 Survivor Space가 존재함
2_4. Old Generation
- 오랫동안 살아남은 객체가 옮겨지는 곳
2_5. Generation 별 GC 동작 방식
- Young Collection (Minor GC)
- Eden이 가득찼을 때 발생
- 항상 STW 유발
- 사용하지 않는 객체가 많으므로 Old Collection과 비교해서 상대적으로 매우 빠름
- Old Collection (Major GC, Full GC)
- Old Generation이 가득찼을 때 (또는 일정 이상 찼을 때) 발생
- 알고리즘에 따라 항상 STW가 발생하거나 덜 발생함
- Young Collection에 비해 느림
2_6. GC의 기본동작 3가지
- mark
- 사용 중인 객체 표시
- sweep
- 사용하지 않는 객체 제거
- compaction
- 모든 객체들이 연속된 공간에 위치하도록 재배치함
3. GC 기본 동작
3_1. Young Collection
- Filling the Eden Space
- Eden에서 새로운 객체가 만들어짐
- Object Aging
- Eden이 가득차면 Young Collection이 일어나며 Eden을 비움
- 사용하지 않는 객체는 지워짐
- 사용 중인 객체는 바로 Old Generation으로 가지 않고 Survivor Space에서 머뭄
- Survivor Space는 매 GC 사이클마다 From, To 역할을 번갈아가면서 수행
- Survivor Space 한 곳으로 사용 중인 객체를 옮기면서 자연스럽게 Compaction도 하게됨
- Promotion
- 충분히 나이를 먹은 객체는 Old Generation으로 보내짐 → Promotion
- 만약 To Survivor Space가 가득차서 더 이상 객체를 받을 수 없다면 바로 Old Generation으로 보내짐 → Premature Promotion
- Premature Promotion은 Old Collection에 영향을 주므로 최대한 피하는 게 좋음
3_2. Old Collection
- 사용 중인 객체가 많으면 GC에 많은 시간이 걸림
- 알고리즘에 따라서 다르게 동작하고 사용 목적, 튜닝 방법도 다름
3_3. 힙 크기와 Generation 크기 튜닝
- 대부분의 객체가 잠깐동안 사용되고 사라짐
- Young Generation의 크기를 늘려주면 성능이 향상될 수 있음
- 대부분의 객체가 오랫동안 사용됨
- Young Generation의 크기를 줄이거나
- Promotion이 빨리 되도록 하면 성능이 향상될 수 있음
- 최소 힙 크기
- XmsN
- 최대 힙 크기
- XmxN
- Generation 크기 조절
- -XX:NewRatio=3
- (Young : Old = 1 : 3, Young Generation은 전체 힙 크기에서 1 / 4)
- -XX:SurvivorRatio=6
- (Eden : Survivor = 1 : 6, 각 Survivor Space는 Young Generation의 1/8)
- -XX:SurvivorRatio=6
- Promotion 비율 조절
- -XX:MaxTenuringThreshold=
- 기본값은 15
- -XX:MaxTenuringThreshold=
- GC 로그 옵션 플래그
- 간단한 GC 로그
-
-verbose:gc or -XX:+PrintGC
- 더 자세한 로그
-
-XX:+PrintGCDetails
- 힙 크기 조정 로그
-
-XX:-PrintAdaptiveSizePolicy
- Tenuring Information
-
-XX:-PrintTenuringDistribution
- 시간 정보
-
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
- Log rotation
-
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=N -XX:GCLogFileSize=N
- 간단한 GC 로그
-
- 기타 유용한 툴
-
튜닝 플래그 확인 -
jcmd <process_id> VM.flags [-all]
-java <other_option> -XX:+PrintFlagsFinal -version
-
실행 커맨드 확인 -
jcmd <process_id> VM.command_line
-
실행 중 튜닝 플래그 바꾸기 (제한적으로 가능) -
jinfo -flags +PrintGCDetails <process_id>
-
4.GC 알고리즘
- Serial Collector
- Throughput Collector
- Concurrent Collector
- CMS Collector
- G1 Collector
4_1. Serial Collector
- 가장 단순한 형태의 Collector로 단일 쓰레드로 동작
- 시스템이 클라이언트 클래스라면 디폴트로 사용
- Young Collection, Old Collection 모두 STW 발생
- GC는 mark, sweep, compaction를 거쳐서 힙을 완전히 압축함
-
java -XX:+UseSerialGC -jar my-app.jar …
- 사용할 때
- Serial Collector: 힙 크기가 100MB 미만
- 그 외 경우에는 Throughput Collector와 Concurrent Collector 중 하나를 고르게 됨
4_2. Throughtput Collector
- Parallel Collector라고도 부름
- 시스템이 server class라면 디폴트로 사용
- 멀티 쓰레드로 동작
- Young Collection, Old Collection 모두 STW 발생
- GC는 mark, sweep, compact를 거쳐서 힙을 완전히 압축함
java -XX:+UseParallelGC -XX:+UseParallelOldGC -jar my-app.jar …
- JDK 7u4 이상부터는 UseParallelGC 플래그를 사용하면 기본적으로 UseParallelOldGC 플래그를 사용함
4_3. Concurrenct Collector
- 멀티 쓰레드로 동작
- Young Collection은 STW 발생
- Old Collection은 mark 과정 일부에서 STW 발생시키고 나머지는 프로그램 쓰레드와 동시에 진행됨
- 따라서 중지 시간이 짧지만 (응답 속도가 빠름) 백그라운드 쓰레드를 위한 CPU 자원이 필요함
- Old Collection은 Old Generation이 일정 이상 차기 시작했을 때 시작함
- 대표적으로 CMS와 G1가 있음
4_3_1. CMS Collector
- CMS는 “Concurrent Mark Sweep”의 줄임말
- Old Generation을 압축하지 않아서 힙 단편화가 발생 (compaction 없음)
- CMS에서 주의해야할 부분은 Concurrent Mode Failure로 두 가지 이유 때문에 발생
- 힙 단편화로 더 이상 객체를 할당할 수 없거나
- Old Collection이 끝나기 전에 많은 Promotion으로 Old Generation이
- 가득차면 Full GC가 시작되면서 성능이 크게 저하됨 (이 때 힙을 압축함)
java -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -jar my-app.jar …
4_3_2. G1 Collector
- G1은 “Garbage First”의 줄임말
- JDK 6, 7에서는 시험적으로 사용
- JDK 8u40부터 production 환경에서 사용 가능할 정도로 안정화됨
- JDK 9부터는 기본적으로 G1 Collector를 사용
-XX:MaxGCPauseMillis=200
- G1 GC의 목표
- 4GB 이상의 큰 힙을 빠르게 처리
- 중지 시간을 설정하고 예측 가능
- 장기적으로 CMS를 대체하려는 목적도 있음
- Old Generation을 압축하지 않아서 힙 단편화가 발생 (compaction 없음)
- CMS처럼 Concurrent Mode Failure가 발생할 수 있음
-
java -XX:+UseG1GC -jar my-app.jar …
- 중요한 특징들
- 힙을 Region (1MB ~ 32MB)로 나눔
- 각 Region은 정해진 목적없이 필요할 때 Eden, Survivor Space, Old Generation이 될 수 있음
- Generation별 Region은 인접할 필요없음
- Young Collection의 기본 동작은 다른 알고리즘과 차이 없음
- G1 GC의 Young Collection
- G1 GC는 기본적으로 GC 작업 중 사용 중인 객체를 하나의 Region으로 모으려고 함 (부분적인 Compaction)
- 그 외에 다른 알고리즘과의 큰 차이점은 없음
- G1 GC의 Old Collection
- 최대 중지 시간 목표 안에서 가장 Garbage가 많은 Region을 우선적으로 비움
- 모든 Garbage를 다 처리하지 않을 수 있음
- 사용 중인 객체를 하나의 Region으로 모으는 작업으로 부분적인 compaction 효과 있음
5. GC 알고리즘 비교
- Throughput Collector가 좋을 때
- 충분한 CPU가 없음
- Concurrent Collector가 사용할 CPU 자원이 없음
- 평균 응답 시간이 90 percentile, 99 percentile보다 더 중요할 때
- 또는 응답 시간이 중요하지 않을 때
- Concurrent Collector가 좋을 때
- CPU를 충분히 사용할 수 있음
- 평균 응답 시간보다 90 percentile, 99 percentile이 더 중요할 때
- 힙이 커서 Throughput Collector가 Old Collection 작업에 오랜 시간이 걸릴 때
- CMS vs G1
- CMS가 나을 때 - 힙이 크지 않을 때 - JDK 8u40 보다 낮은 버전을 사용 중일 때
- G1이 나을 때 - 힙이 4GB 이상으로 클 때 - 다음 조건을 하나 이상 만족하는 경우 CMS에서 G1으로 옮겼을 때 이득을볼 수 있음 - 50% 이상의 힙이 살아있는 객체로 채워짐 - 객체 할당 비율이나 promotion 비율이 크게 달라짐 - 애플리케이션의 GC로 인한 중지 시간이 0.5초에서 1초 이상
Subscribe via RSS