[Java Optimizing] 7. 가비지 수집 고급

장현진·2022년 10월 2일
0

트레이드오프와 탈착형 수집기

개발자는 가비지 수집기 선정 시 다음 항목을 고려해야합니다.

  • 중단 시간(중단 길이 또는 기간이라고도 합니다.)
  • 처리율(애플리케이션 런타임 대비 GC 시간)
  • 중단 빈도(수집기 때문에 애플리케이션이 얼마나 자주 멈추는지)
  • 회수 효율(GC 사이클 당 얼마나 많은 가비지가 수집되는지)
  • 중단 일관성(중단 시간이 고른편입니다.)

성능 엔지니어는 수집기 선정 시 다양한 트레이드 오프와 관심사를 면밀히 검토해야합니다.

JVM 세이프포인트

핫스팟 병렬 수집기에서 STW 가비지 수집을 실행하려면 애플리케이션 스레드를 모두 중단시켜야합니다.

JVM은 사실 완전히 선제적인 멀티스레드 환경이 아님. OS 코어 기능처럼 JVM도 조정 작업이 필요.

애플리케이션 스레드마다 세이프포인트(안전점, safe point)라는 특별한 지점을 둡니다.

세이프포인트는 스레드의 내부 자료 구조가 보이는 지점이며, 이때 어떤 작업을 하기 위해 스레드는 잠시 중단될 수 있습니다.

JVM은 다음 두가지 규칙에 따라 세이프포인트를 처리합니다.

  • JVM은 강제로 스레드를 세이프포인트 상태로 바꿀 수 없다.
  • JVM은 스레드가 세이프포인트 상태에서 벗어나지 못하게 할 수 있다.

따라서 세이프포인트 요청을 받았을 때 그 지점에서 스레드가 제어권을 반납하게 만드는 코드가 VM 인터프리터 구현체 어디에 잇어야합니다. (세이브 포인터는 JIT컴파일러에의해 코드에 삽입된다.)

  1. JVM이 전역을 세이프포인트 시간 플래그를 세팅합니다.

  2. 각 애플리케이션 스레드는 폴링(주기적으로 세이브 포인트 검사)을 하면서 이 플래그가 세팅됐는지 확인합니다.

  3. 애플리케이션 스레드는 일단 멈췄다가 다시 깨어날 때까지 대기합니다.


세이프포인트 시간 플래그를 세팅하면 모든 애플리케이션 스레드는 반드시 멈춰야합니다. 일반 애플리케이션 스레드는 이런 식으로 풀링


다음의 경우, 스레드는 자동으로 세이프포인트 상태가 됩니다.
  • 모니터에서 차단되는 경우
  • JNI 코드를 실행하는 경우

다음의 경우, 스레드가 꼭 세이프포인트 상태가 되는 것은 아닙니다.

  • 바이트코드를 실행하는 도중(인터프리티드 모드)
  • OS가 인터럽트를 거는 경우


삼색 마킹

삼색 마킹 알고리즘은 다음 순으로 진행됩니다.

  1. GC 루트를 흰색 표시합니다.
  2. 다른 객체는 모두 흰색 표시합니다.
  3. 마킹 스레드가 회색 노드로 랜덤하게 이동합니다.
  4. 이동한 노드를 검은색 표시하고 이 노드가 가리키는 모든 흰색 노드를 회색 표시합니다.
  5. 회색 노드가 하나도 남지 않을 때까지 위 과정을 되풀이합니다.
  6. 검은색 객체는 모두 접근 가능한 것이므로 살아남습니다.
  7. 흰색 노드는 더 이상 접근 불가한 객체이므로 수집 대상이 됩니다.
  • BFS탐색을 통해 객체들을 탐색

CMS(Concurrent Mark & Sweep) GC : 14이후 drop

CMS 수집기(동시 수집)는 중단 시간을 아주 짧게 하려고 설계된 테뉴어드(올드) 공간 전용 수집기 입니다. 보통 Parallel GC와 함께 사용합니다.

CMS는 중단 시간을 최소화하기 위해 애플리케이션 스레드 실행 중에 가급적 많은 일을 합니다. 따라서 일부 수행 단계는 좀 더 복잡합니다.

  1. 초기 마킹(STW)
  • GC Root나 Young Generation에서 참조하는 객체를 회색으로 마킹한다.
    이렇게 마킹해놓으면 마킹 단계에서 다른 메모리 영역은 신경쓰지 않고 하나의 GC 풀에만 신경쓰면 된다.
    =( 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다.STW가 매우 짧다. )
    =( 모든 테뉴어드가 스택영역에 연결되어 있지는 않음 )
  1. 동시 마킹
  • 마킹 쓰레드와 어플리케이션 쓰레드가 동시에 돌기 때문에 객체 참조 간에 변경 사항이 발생한다.
    =( 초기 마킹을 바탕으로 삼색 마킹 알고리즘을 힙에 적용하면서 나중에 조정해야 할지 모를 변경 사항을 추적한다. )
  1. 동시 사전 정리
  • Old Generation 영역을 일정 공간으로 나누어 Card라 불리는 곳에 저장한다.
    그리고 Initial Mark 이후에 참조 간에 변경이 생긴 곳을 Dirty Card라고 부른다.
  • 사전 정리를 해야 Dirty Card에서 일반 Card가 될 수 있다.
    이 phase의 목적은 5단계(재마크, Final Remark) 시에 STW 시간을 줄이기 위함이다.

=( 카드 테이블을 이용해 변경자 스레드가 동시 마킹 단계 도중 영향을 끼친 마킹을 조정한다. )
=( young객체와 연결된경우 살려야 한다.)

카드 테이블
: Old 영역에는 512바이트의 덩어리(chunk)로 되어있는 card table이 존재한다.
card table에는 old 영역에 있는 객체가 young 영역의 객체를 참조할 때마다 정보가 표시된다. young영역의 GC를 실행할 때에는 old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC대상인지 식별한다.

  1. 재마킹(STW)
  • Old Generation의 모든 live object를 마킹한다.
  1. 동시 스위프

=(쓰레기를 정리하는 작업을 실행한다.)

  • 일반적으로 CMS는 여러 정지 단계가 연속적으로 발생할 가능성을 제거하기 위해 Young Generation이 가능한 한 비어 있을 때 최종 발언 단계를 실행하려고 함.
  1. 동시 리셋

장단점

  • 애플리케이션 스레드가 오랫동안 멈추지않습니다.
  • 단일 풀 GC 사이클 시간이 더 깁니다.(발생 주기가 적게 발생한다.)
  • CMS GC 사이클이 살행되는 동안, 애플리케이션 처리율은 감소합니다.(병렬실행)
  • GC가 객체를 추적해야 하므로 메모리를 더 많이 사용합니다.
  • GC 수행에 훨씬 더 많은 CPU 시간이 필요합니다.
  • CMS는 힙을 압착하지 않으므로 테뉴어드 영역은 단편화 될 수 있습니다.


G1

  • G1GC는 Garbage-First Garbage Collector를 줄여쓴 것

G1은 이름을 보면 짐작할 수 있듯 쓰레기로 가득찬 heap 영역을 집중적으로 수집한다.
G1은 큰 메모리를 가진 멀티 프로세서 시스템에서 사용하기 위해 개발된 GC이다.
GC 일시 정지 시간을 최소화하면서 따로 설정을 하지 않아도 가능한 한 처리량(throughput)도 확보하는 것이 G1GC의 목표이다.

가비지 우선은 병렬 수집기나 CMS와는 전혀 다른 스타일의 수집기입니다. 다음의 특징이 있습니다.

  • CMS보다 튜닝하기 쉽습니다.
  • 조기 승격에 덜 취약합니다.
  • 대용량 힙에서 확장성(특히, 중단 시간)이 우수합니다.
  • 풀 STW 수집을 없앨 수 있습니다.

G1은 Java 9부터 디폴트 GC이다. G1은 실시간(real time) GC가 아니다.
일시 정지 시간을 최소화하긴 하지만 완전히 없애지는 못한다.
G1은 통계를 계산해가면서 GC 작업량을 조절한다.

다음 상황이라면 G1을 쓰면 도움이 된다.

Java heap의 50% 이상이 라이브 데이터.
시간이 흐르면서 객체 할당 비율과 프로모션 비율이 크게 달라진다.
GC가 너무 오래 걸린다(0.5 ~ 1초).

다른 GC와의 비교

Parallel GC
Parallel GC는 old gen의 공간에서만 재확보(reclaim)와 조각 모음(compaction)을 한다.
G1은 이런 작업을 더 짧은 GC 작업들로 분배하여 수행하여, 전체적인 처리량이 줄어드는 대신 일시 정지 시간을 크게 단축한다.

CMS
G1도 CMS처럼 old gen 영역을 동시에(concurrently) 작업한다.
CMS는 old gen의 조각 모음을 하지 않으므로 Full GC 시간이 길어지는 문제가 있다.

G1 힙 레이아웃 및 영역

G1 힙은 영역(리전)으로 구성됩니다.

  • 영역별로 각각의 스레드가 개별로 동작한다.
    = 한번 동작시 모든 테뉴어드공간을 살펴 보지 않음 -> 중단 시간 감소

영역 크기 : <힙 크기> / 2048
영역 개수 : <힙 크기> / <영역 크기>

G1 알고리즘 설계

G1수집기는 다음의 역할을 수행합니다.

  • 동시 마킹 단계를 이용합니다.
  • 방출 수집기입니다.
  • 통계적으로 압착합니다.

G1 수집기는 워밍업을 하는 동안, GC 사이클이 한번 돌때마다 많은 '일반' 영역에서 가비지를 수집할 수 있는지 그 수치를 보관합니다.

G1 수집기는 TLAB 할당이나 서바이버 공간으로 방출, 테뉴어드 영역으로의 승격 등은 앞서 나온 다른 핫스팟 수집기와 대동소이하지만, 세대를 구성하는 영역이 연속되지 않는다는 단점이 있습니다.

G1 수집기에도 기억 세트(RSet, remembered set)라는 비슷한 장치로 영역을 추적합니다. RSet은 영역별로 하나씩, 외부에서 힙 영역 내부를 참조하는 레퍼런스를 관리하기 위한 장치입니다.

G1 단계

G1의 수집단계는 다음과 같습니다.

  1. 초기 마킹 STW
    = Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다(STW)
  2. 동시 루트 탐색
    =위에서 찾은 Survivor 객체들에 대한 스캔 작업을 실시한다
  3. 동시 마킹
    = 전체 Heap의 scan 작업을 실시하고, GC 대상 객체가 발견되지 않은 Region은 이후 단계를 제외한다
  4. 재마킹 STW
    =애플리케이션을 멈추고(STW) 최종적으로 GC 대상에서 제외할 객체를 식별한다
  5. 정리
    =애플리케이션을 멈추고(STW) 살아있는 객체가 가장 적은 Region에 대한 미사용 객체를 제거한다
  6. 복사
    =GC 대상의 Region이었지만, Cleanup 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운 Region(Available/Unused) Region에복사하여 Compaction을 수행한다
    살아있는 객체가 아주 적은 Old 영역에 대해 [GC pause(mixed)] 를 로그로 표시하고, Young GC가 이루어질 때 수집되도록 한다

GC Cycle

G1은 두 페이즈를 번갈아 가며 GC 작업을 한다.

young-only 페이즈: old 객체를 새로운 공간으로 옮긴다.

Young Only (Garbage Collection) Phase는 Minor GC만 수행하다가 XX:InitiatingHeapOccupancyPercent(Old Generation 비율)에 지정된 값을 초과하는 순간 Major GC가 수행된다. 그림에서 Old gen occupancy exceeds threshold 부분을 말하는 것이다.
Major GC의 첫 단계는 Initial Mark이며 Minor GC와 동시에 수행되며 둘 다 STW를 수반하므로 다른 파란 원보다 크기가 크다. 그 이후에 애플리케이션 스레드, Minor GC, Concurrent Mark가 동시에 수행되는데 Remark가 수행되는 순간 다른 작업은 멈추게 된다. 그래서 Remark에 해당하는 주황색이 원이 큰 것을 알 수 있다. 그 이후에 자잘하게 Minor GC가 수행되다가 Major GC의 Cleanup이 발생한다.

space-reclamation 페이즈: 공간 회수.

Young Only Phase가 끝나고 Space Reclamation(공간 회수) Phase가 시작된다. 해당 Phase에서는 Mixed GC가 수행되는데 Mark 단계가 없어서 STW 빈도가 Young Only Phase에 비해 줄어든 것을 알 수 있다. Space Reclamation Phase가 끝나면 다시 Young Only Phase로 돌아가서 Minor GC를 수행한다.

old gen의 점유율이 threshold 값을 넘어서면 Young-only 페이즈로 전환된다.

Concurrent Start 단계: 도달할 수 있는 객체들에 마킹 작업을 한다.
Remark: 마킹을 끝내고, 쓰레기 영역을 해지한다.
Cleanup: Space-Reclamation 페이즈로 들어가야 할지 말지를 판단한다.
Space-Reclamation: young/old 가리지 않고 라이브 객체를 적절한 곳으로 대피시킨다(Evacuation). 작업 효율이 떨어지게 되면 이 페이즈는 끝나고, 다시 Young-only 페이즈로 전환된다.

만약 애플리케이션 메모리가 부족한 경우 G1GC는 다른 GC들처럼 Full GC를 수행한다.

G1GC의 마킹

G1GC는 SATB(Snapshot-At-The-Beginning) 알고리즘을 써서 마킹 작업을 한다.
SATB는 일시 정지가 일어난 시점 직후의 라이브 객체에만 마킹을 한다.
따라서 마킹하는 도중에 죽은 객체도 라이브 객체로 간주하는 보수적인 특징이 있다.
비효율적일 것 같지만 Remark 단계의 응답 시간(latency)이 다른 GC에 비해 더 빠른 경향이 있다.

기본 JVM 플래그

+XX:UseG1GC
G1의 주목표는 중단 시간 단축입니다. 따라서 최대 중단 시간을 개발자가 선정할 수 있는데, 값을 너무 잡게 잡으면 GC 서브시스템이 목표에 맞추지 못할 것입니다.

결론적으로는 8u40부터 사용하는 것이 지향되며, 저지연 워크로드에서는 아직 G1은 CMS 중단 시간보다 긴 편이라 아직까지는 긴편입니다. 다만 여전히 개선되고 있습니다.

결론

가바지 수집기는 플랫폼의 강력한 장점이지만, 이를 잘알고 수집기 별로 고려해야하는 트레이드 오프를 아는 것이 중요합니다.

https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep/cms-full-gc
https://velog.io/@ha0kim/GCGarbage-Collection-2
https://huisam.tistory.com/entry/jvmgc
https://steady-coding.tistory.com/590

0개의 댓글