book/자바 최적화

[자바 최적화] 가비지 수집 고급

danuri 2023. 8. 29. 22:11

자바 최적화 책 정리

 

자바 최적화(Optimizing Java) | 벤저민 J. 에번스 - 교보문고

자바 최적화(Optimizing Java) | 자바 애플리케이션 성능을 한 단계 높여줄 튜닝 이야기성능 튜닝은 실험과학이다. 추측과 구전 튜닝에 의존할 일이 아니다. 이 책은 복잡한 기술 스택을 다루는 중/고

product.kyobobook.co.kr

 

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

✅ GC의 트레이드오프

개발자는 가비지 수집기 선정 시 다음 항목을 충분히 고민해야 한다.

  • 중단 시간
  • 처리율
  • 중단 빈도
  • 회수 효율
  • 중단 일관성

-> 애플리케이션의 특징에 따라 이러한 트레이드오프와 관심사를 면밀히 검토해야 한다.

-> 또한, GC는 탈착형 서브시스템으로 취급되기 때문에, 각 JVM 구현체와 애플리케이션의 특징에 따른 적절한 수집기를 선택할 수 있다.

 


 

동시 GC 이론

JVM 세이브포인트

✅ 세이브포인트

JVM이 STW 가비지 수집을 위해 애플리케이션 스레드를 모두 중단시켜야 한다.

그래서 애플리케이션 스레드마다 세이브포인트(안전점)라는 특별한 실행 지점을 둔다.

 

✅ JVM 세이브포인트 처리 규칙

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

-> 따라서 스레드는 세이브포인트 요청을 받았을 때, 그 지점에서 스레드가 제어권을 반납하게 만드는 코드가 VM 인터프티러 구현체 혹은 JIT 컴파일한 메서드에 있어야 한다.

 

✅ 세이브포인트 상태로 바뀌는 일반적인 경우

  1. JVM이 전역 '세이브포인트 시간' 플래그를 세팅한다.
  2. 각 애플리케이션 스레드는 폴링(스레드가 플래그를 주기적으로 확인)하면서 이 플래그가 세팅됐는지 확인한다.
  3. 애플리케이션 스레드는 일단 멈췄다가 다시 깨어날 때까지 대기한다.

 

✅ 구체적인 세이브포인트 사례

다음 각 경우에 스레드는 자동으로 세이브포인트 상태가 된다.

  • 모니터에서 차단된다.
  • JNI(Java Native Interface) 코드를 실행한다.

다음 경우에는 스레드가 꼭 세이브포인트 상태가 되는 건 아니다.

  • 바이트코드를 실행하는 도중(인터프리티드 모드)이다.
  • OS가 인터럽트를 걸었다.

 

삼색 마킹

✅ 삼색 마킹 알고리즘

  • GC 루트를 회색 표시한다.
  • 다른 객체는 모두 흰색 표시한다.
  • 마킹 스레드가 임의의 회색 노드로 이동한다.
  • 마킹 스레드가 흰색 표시된 자식 노드가 있는 노드를 만나면, 먼저 그 자식 노드를 모두 회색 표시한 뒤, 해당 노드를 검은색 표시한다.
  • 회색 노드가 하나도 남지 않을 때까지 위 과정을 되풀이한다.
  • 검은색 객체는 모두 접근 가능한 것이므로 살아남는다.
  • 흰색 노드는 더 이상 접근 불가능한 객체이므로 수집 대상이 된다.

 

 

✅ 동시 수집기와 삼색 마킹 알고리즘

동시 수집기에 삼색 마킹 알고리즘을 사용하려면 고려해야 할 사항이 있다.

바로 변경자 스레드(힙에 위치한 객체를 변경하는 스레드)와 삼색 마킹 알고리즘의 마킹 스레드가 함께 수행되야 할 때는 어떻게 해야 할까?

-> 가령, 변경자 스레드가 수집을 하는 도중에는 검은색 상태, 수집을 안하는 동안에는 흰색 상태로 새 객체를 생성할 수 있겠다.

 

또한, 마킹 스레드가 수집을 하는 도중, 변경자 스레드가 기존 흰색 객체를 특정 검정 객체가 참조하도록 했다면?

-> 해당 흰색 객체는 아직 접근 가능하지만, 삼색 마킹 알고리즘 규칙에 따라 발견되지는 않을테니 결국 삭제될 것이다.

-> 이 문제는 검은색 객체 색깔을 다시 회색으로 변경해서 마킹 스레드가 처리할 노드 세트에 다시 추가하는 방법이 있다.

-> 즉, 동시 마킹 도중에는 절대로 검은색 객체 노드가 흰색 객체 노드를 가리킬 수 없도록 하는 것이다.

 


 

CMS

✅ CMS(Concurrent Mark Sweep)

CMS 수집기는 중단 시간을 아주  짧게 하려고 설계된, 테뉴어드 공간 전용 수집기다.

-> 중단 시간을 최소화하기 위해, 애플리케이션 스레드 실행 중에 가급적 많은 일을 한다.

 

✅ CMS 수행 단계

  1. 초기 마킹(STW): 확실한 GC 출발점을 얻는 것이 목적. 마킹 단계에서는 다른 메모리 영역은 신경 쓸 필요 없다.
  2. 동시 마킹: 삼색 마킹 알고리즘을 힙에 적용.
  3. 동시 사전 정리: 재마킹 단계에서 가능한 한 STW 시간을 줄이도록 정리함.
  4. 재마킹(STW): 카드 테이블을 이용해 변경자 스레드가 동시 마킹 단계 도중 영향을 끼친 마킹을 조정.
  5. 동시 스위프
  6. 동시 리셋

-> 한 차례 긴 STW 중단을 매우 짧은 두 차례 STW 중단으로 대체한 셈이다.

 

✅ CMS의 효험

  • 애플리케이션 스레드가 오랫동안 멈추지 않는다.
  • 단일 풀 GC 사이클 시간이 더 길다.
  • GC가 객체를 추적해야 하므로 메모리를 더 많이 쓴다.
  • 힙을 압착하지 않으므로 테뉴어드 영역은 단편화될 수 있다.

-> 장단이 있다.

 

CMS 작동 원리

 ✅ 영 GC와 CMS

영 GC가 끝나고 일부 객체는 테뉴어드로 승격되는데, CMS가 올드 수집을 통해 테뉴어드 공간을 정리한다.

그런데 만약 할당률이 급증한다면 영 수집 시 조기 승격이 일어나게 된다.

-> 급기야 영 수집 후 승격된 객체가 너무 많아 테뉴어드 공간조차 부족한 사태가 벌어진다.

 

✅ 동시 모드 실패(CMF)

위와 같은 현상을 CMF라고 하며, JVM은 어쩔 수 없이 풀 STW를 유발하는 ParallelOld GC 수집 방식으로 돌아간다.

-> 할당률이 너무 높아 승격된 객체를 CMS가 처리할 시간이 없는 상태.

 

CMF가 자주 일어나지 않게 하려면?

1. 테뉴어드가 꽉차기 전에 CMS가 수집 사이클을 개시

CMS가 동작하는 디폴트 힙 점유 수준은 75% 테뉴어드다.

2. 힙 단편화 방지

ParallelOld GC와 달리, CMS는 테뉴어드를 압착하지 않는다.

-> 단편화의 원인.

CMS는 프리 리스트를 이용해 사용 가능한 빈 공간을 관리하는데,

이 때, 동시 스위퍼 단계에서 스위퍼 스레드가 여유 공간을 더 큰 덩어리로 만든다.

 

CMS 기본 JVM 플래그

✅ CMS 플래그

CMS 수집기는 다음 플래그로 작동한다.

-XX:+UseConcMarkSweepGC

 


 

G1

✅ G1의 특성

중단 시간이 짧은 수집기로 설계된 G1은 다음과 같은 특성이 있다.

  • CMS보다 훨씬 튜닝하기 쉽다.
  • 조기 승격에 덜 취약하다.
  • 대용량 힙에서 확장성(특히, 중단 시간)이 우수하다.
  • 풀 STW 수집을 없앨 수(또는 풀 STW 수집으로 되돌아갈 일을 확 줄일 수) 있다.

 

G1 힙 레이아웃 및 영역

✅ 영역(리전)

G1 힙은 영역으로 구성된다.

영역은 디폴트 크기가 1메가바이트인 메모리 공간이다.

 

 

G1 알고리즘 설계

✅ G1 수집기가 하는 일

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

-> 기본적으로 에덴, 서버이버, 테뉴어드의 개념은 다른 수집기와 대동소이하다.

-> 차이점으로는 세대를 구성하는 영역이 연속되어 있지 않다는 점이다.

 

✅ 기억 세트(RSet)

핫스팟 병렬/CMS 수집기의 카드 테이블처럼,

G1 수집기에도 RSet이라는 비슷한 장치로 영역을 추적한다.

-> RSet은 영역별로 하나씩, 외부에서 힙 영역 내부를 참조하는 레퍼런스를 관리하기 위한 장치다.

-> 덕분에 G1은 영역 내부를 바라보는 레퍼런스를 찾으려고 전체 힙을 다 뒤질 필요 없이 RSet만 꺼내 보면 된다.

 

G1 단계

✅ G1의 수집 단계

  1. 초기 마킹(STW)
  2. 동시 루트 탐색: 서바이버 영역에서 올드 세대를 가리키는 레퍼런스를 찾는 단계
  3. 동시 마킹
  4. 재마킹(STW)
  5. 정리(STW): 어카운팅(재사용 준비를 마친 영역을 식별), RSet 씻기

 

G1 기본 JVM 플래그

✅ G1 플래그

<자바 8 이전까지 G1GC 플래그>

+XX:UseG1GC

(자바 9부터는 디폴트 수집기가 G1GC)

 

<디폴트 중단 시간 목표 설정>

-XX:MaxGCPauseMillis=200

 

<디폴트 영역 크기 값 설정>

-XX:G1HeapRegionSize=<n> // n: 1부터 64까지의 2의 제곱수 (단위: MB)

 


 

셰난도아

✅ 셰난도아 수행 단계

셰난도아: 레드햇에서 제작한 자체 수집기

  1. 초기 마킹(STW)
  2. 동시 마킹
  3. 최종 마킹(STW)
  4. 동시 압착

 

✅ 브룩스 포인터

객체 당 메모리 워드를 하나 더 써서, 새 버전 객체 콘텐츠의 위치를 가리킨다.

 

<재배치 전>

 

 

재배치되지 않은 객체의 브룩스 포인터는 그냥 포인터 다음 워드를 가리킨다.

 

<재배치 후>

 

 

재배치가 되면, 기존에 있었던 위치에 브룩스 포인터를 남겨둔다.

이 때 기존 위치의 브룩스 포인터는 객체의 새 위치를 가리킨다.

-> 이렇게 함으로써, 기존 객체를 참조했던 다른 객체들은 브룩스 포인터를 통해 객체의 새 위치를 알 수 있게 된다.

 

동시 압착

✅ 압착 스레드 동작

G1과는 다르게 셰난도아는 압착을 애플리케이션 스레드와 동시에 진행

  1. 객체를 TLAB로 (추측하여) 복사한다.
  2. CAS(동시성 보장)로 브룩스 포인터가 추측성 사본을 가리키도록 수정한다.
  3. 이 작업이 성공하면 압착 스레드가 승리한 것으로, 이후 이 버전의 객체는 모두 브룩스 포인터를 경유해서 엑세스하게 된다.
  4. 이 작업이 실패하면 압착 스레드가 실패한 것으로, 추측성 사본을 원상복구하고 승리한 스레드가 남긴 브룩스 포인터를 따라간다.

 

셰난도아 얻기

✅ 셰난도아 스위치

-XX:+UseShenandoahGC

 

+) 셰난도아 수집기는 일반적인 오라클 자바 빌드 버전에서는 구할 수 없다.

-> 레드햇 페도라 리눅스 배포판에서 사용하는 아이스티 바이너리(OpenJDK)에 실려 있다.

 

✅ 셰난도아 중단 시간 비교

 

 


 

C4(아줄 징)

✅ C4의 객체 헤더

C4: 아줄 시스템의 자바 플랫폼인 징(Zing)의 가비지 수집기

C4는 64비트 워드 하나로 이루어진 객체 헤더를 사용한다. (징은 64비트 아키텍처 전용이다, 워드 = 64비트)

 

 

  • kid: klass 포인터 대신
  • lock: 락 정보 보관

 

로드값 배리어

✅ C4의 객체 참조 아이디어

징이 어떤 객체를 참조해야 할 때,다른 스레드가 해당 객체를 재배치하고 있다면,

징은 해당 객체가 재배치될 때까지 기다리다가, 안전하게 해당 객체의 최신 참조를 가져온다.

 

✅ 교대 압착

객체의 실제 물리 메모리 주소를 변경하는 것이 아닌,

가상 메모리와 물리 메모리의 매핑을 조절하여 객체를 압착하는 기술.

-> 페이지를 연속적으로 변경하면서 단편화 문제를 해결한다.

 


 

밸런스드(IBM J9)

✅ 밸런스드 영역

밸런스드: IBM의 자바 플랫폼인 J9의 가비지 수집기

밸런스드는 중단시간을 줄이기 위해,

G1처럼 힙을 여러 개의 영역으로 분할해 각자 독립적으로 관리한다.

 

✅ 부분 가비지 수집(PGC)

에덴 영역을 수집하는 STW 작업.

-> 나이가 더 많이 든 영역도 그럴만한 가치가 있다고 판단되면 추가로 수집한다.

-> 밸런스드는 PGC 도중 클래스 언로딩을 수행할 수도 있다.

 

✅ 전역 마킹 단계(GMP)

PGC는 스스로 수집하기로 결정한 영역만 바라볼 수 있으므로 수집되지 않는 죽은 객체가 생길 수 있다.

밸런스드는 GMP를 통해 전체 자바 힙을 탐색하면서 수집할 죽은 객체를 표시한다.

-> GMP가 끝나고 해당 데이터에 PGC를 수행한다.

 

✅ 전역 가비지 수집(GGC)

풀 STW 수집.

-> 핫스팟에서 CMF 발생 시 일어나는 풀 수집과 비슷하다.

 

J9 객체 헤더

✅ 클래스 슬롯

J9의 기본 객체 헤더

 

 

 

-> 클래스 포인터: 클래스 구조를 가리키는 포인터 (오프 힙 메모리를 가리킨다)

 

밸런스드에서 큰 배열 처리하기

✅ 어레이릿

밸런스드 수집기가 큰 배열을 여러 영역에 걸쳐 할당할 수 있도록 하는 형태.

큰 배열은 스파인배열 리프들로 표현한다.

-> 스파인은 배열 리프들을 가리키는 자료구조이고,

-> 배열 리프는 실제 배열 값들을 담고 있다.

 

 

-> 스파인 + 배열 리프를 같이 관리하므로 전체 GC 소요 시간은 더 걸리지만 평균 중단 시간은 줄어든다.

-> 밸런스드는 처리율을 높이는 것보다 중단 시간을 줄이는 일이 더 중요한 애플리케이션에 적합하다.

 

NUMA와 밸런스드

✅ NUMA

프로세스와 가까운 메모리를 노드로 묶는 설계 방법.

-> 어떤 노드에 있는 프로세스가 다른 노드에 있는 메모리에 액세스 하는 것 보다,

-> 로컬 메모리(같은 노드에 속한)에 액세스할 때가 가장 빠르다.

 

✅ 노드와 밸런스드

밸런스드는 노드 별로 자바 힙을 분리할 수 있다.

-> 가급적 같은 노드에 객체의 할당/수집이 일어나도록 해서 성능을 좋게 한다.

 


 

레거시 핫스팟 수집기

✅ 이 밖에 다양한 수집기

앞으로 소개할 수집기는 운영계 용도로는 적합하지 않으니 가급적 사용하지 말자.

 

Serial 및 Serial Old

✅ 한 코어만 사용하는 GC

Parallel/ParallelOld GC와 작동 원리는 거의 같지만,

CPU 한 코어만 사용해 GC를 수행하기에 성능이 매우 안좋으니 사용하지 말자.

 

증분 CMS(iCMS)

✅ STW 없이 전체 동시 수집을 시도한 수집기

코어가 한 두개 뿐인 낡은 애플리케이션에는 적합할 수 있지만,

요즘 서버급 애플리케이션에서는 사용하지 않는게 좋다.

 

디프리케이트되어 사라진 GC 조합

✅ 두고두고 참고하자

DefNew + CMS -XX:-UseParNewGC -XX:+UseConcMarkSweepGC
ParNew + SerialOld -XX:+UseParNewGC
ParNew + iCMS -Xincgc
ParNew + iCMS -XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGC
DefNew + iCMS -XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGC -XX:-UseParNewGC
CMS foreground -XX:+UseCMSCompactAtFullCollection
CMS foreground -XX:+CMSFullGCsBeforeCompaction
CMS foreground -XX:+UseCMSCollectionPassing

 

엡실론

✅ 운영계 환경에서는 절대 사용 금물

엡실론은 테스트 전용으로 설계된, 아무 일도 안하는 시험 수집기다.

-> 가비지 수집 활동을 일체 하지 않는다.

 

그럼 이걸 왜 사용하지?

-> 테스트 및 마이크로벤치마크 등 GC의 영향을 배제한 테스트를 하기 위해서

 


 

마치며

✅ 여러 가비지 수집기의 트레이드오프

가비지 수집은 자바 성능 및 튜닝에서 정말 중요한 요소다.

가비지마다 트레이드오프와 성능을 꼼꼼하게 잘 따져,

애플리케이션에 적합한 수집기를 선택하자.