자바 최적화 책 정리
트레이드오프와 탈착형 수집기
✅ GC의 트레이드오프
개발자는 가비지 수집기 선정 시 다음 항목을 충분히 고민해야 한다.
- 중단 시간
- 처리율
- 중단 빈도
- 회수 효율
- 중단 일관성
-> 애플리케이션의 특징에 따라 이러한 트레이드오프와 관심사를 면밀히 검토해야 한다.
-> 또한, GC는 탈착형 서브시스템으로 취급되기 때문에, 각 JVM 구현체와 애플리케이션의 특징에 따른 적절한 수집기를 선택할 수 있다.
동시 GC 이론
JVM 세이브포인트
✅ 세이브포인트
JVM이 STW 가비지 수집을 위해 애플리케이션 스레드를 모두 중단시켜야 한다.
그래서 애플리케이션 스레드마다 세이브포인트(안전점)라는 특별한 실행 지점을 둔다.
✅ JVM 세이브포인트 처리 규칙
- JVM은 강제로 스레드를 세이브포인트 상태로 바꿀 수 없다.
- JVM은 스레드가 세이브포인트 상태에서 벗어나지 못하게 할 수 있다.
-> 따라서 스레드는 세이브포인트 요청을 받았을 때, 그 지점에서 스레드가 제어권을 반납하게 만드는 코드가 VM 인터프티러 구현체 혹은 JIT 컴파일한 메서드에 있어야 한다.
✅ 세이브포인트 상태로 바뀌는 일반적인 경우
- JVM이 전역 '세이브포인트 시간' 플래그를 세팅한다.
- 각 애플리케이션 스레드는 폴링(스레드가 플래그를 주기적으로 확인)하면서 이 플래그가 세팅됐는지 확인한다.
- 애플리케이션 스레드는 일단 멈췄다가 다시 깨어날 때까지 대기한다.
✅ 구체적인 세이브포인트 사례
다음 각 경우에 스레드는 자동으로 세이브포인트 상태가 된다.
- 모니터에서 차단된다.
- JNI(Java Native Interface) 코드를 실행한다.
다음 경우에는 스레드가 꼭 세이브포인트 상태가 되는 건 아니다.
- 바이트코드를 실행하는 도중(인터프리티드 모드)이다.
- OS가 인터럽트를 걸었다.
삼색 마킹
✅ 삼색 마킹 알고리즘
- GC 루트를 회색 표시한다.
- 다른 객체는 모두 흰색 표시한다.
- 마킹 스레드가 임의의 회색 노드로 이동한다.
- 마킹 스레드가 흰색 표시된 자식 노드가 있는 노드를 만나면, 먼저 그 자식 노드를 모두 회색 표시한 뒤, 해당 노드를 검은색 표시한다.
- 회색 노드가 하나도 남지 않을 때까지 위 과정을 되풀이한다.
- 검은색 객체는 모두 접근 가능한 것이므로 살아남는다.
- 흰색 노드는 더 이상 접근 불가능한 객체이므로 수집 대상이 된다.
✅ 동시 수집기와 삼색 마킹 알고리즘
동시 수집기에 삼색 마킹 알고리즘을 사용하려면 고려해야 할 사항이 있다.
바로 변경자 스레드(힙에 위치한 객체를 변경하는 스레드)와 삼색 마킹 알고리즘의 마킹 스레드가 함께 수행되야 할 때는 어떻게 해야 할까?
-> 가령, 변경자 스레드가 수집을 하는 도중에는 검은색 상태, 수집을 안하는 동안에는 흰색 상태로 새 객체를 생성할 수 있겠다.
또한, 마킹 스레드가 수집을 하는 도중, 변경자 스레드가 기존 흰색 객체를 특정 검정 객체가 참조하도록 했다면?
-> 해당 흰색 객체는 아직 접근 가능하지만, 삼색 마킹 알고리즘 규칙에 따라 발견되지는 않을테니 결국 삭제될 것이다.
-> 이 문제는 검은색 객체 색깔을 다시 회색으로 변경해서 마킹 스레드가 처리할 노드 세트에 다시 추가하는 방법이 있다.
-> 즉, 동시 마킹 도중에는 절대로 검은색 객체 노드가 흰색 객체 노드를 가리킬 수 없도록 하는 것이다.
CMS
✅ CMS(Concurrent Mark Sweep)
CMS 수집기는 중단 시간을 아주 짧게 하려고 설계된, 테뉴어드 공간 전용 수집기다.
-> 중단 시간을 최소화하기 위해, 애플리케이션 스레드 실행 중에 가급적 많은 일을 한다.
✅ CMS 수행 단계
- 초기 마킹(STW): 확실한 GC 출발점을 얻는 것이 목적. 마킹 단계에서는 다른 메모리 영역은 신경 쓸 필요 없다.
- 동시 마킹: 삼색 마킹 알고리즘을 힙에 적용.
- 동시 사전 정리: 재마킹 단계에서 가능한 한 STW 시간을 줄이도록 정리함.
- 재마킹(STW): 카드 테이블을 이용해 변경자 스레드가 동시 마킹 단계 도중 영향을 끼친 마킹을 조정.
- 동시 스위프
- 동시 리셋
-> 한 차례 긴 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의 수집 단계
- 초기 마킹(STW)
- 동시 루트 탐색: 서바이버 영역에서 올드 세대를 가리키는 레퍼런스를 찾는 단계
- 동시 마킹
- 재마킹(STW)
- 정리(STW): 어카운팅(재사용 준비를 마친 영역을 식별), RSet 씻기
G1 기본 JVM 플래그
✅ G1 플래그
<자바 8 이전까지 G1GC 플래그>
+XX:UseG1GC
(자바 9부터는 디폴트 수집기가 G1GC)
<디폴트 중단 시간 목표 설정>
-XX:MaxGCPauseMillis=200
<디폴트 영역 크기 값 설정>
-XX:G1HeapRegionSize=<n> // n: 1부터 64까지의 2의 제곱수 (단위: MB)
셰난도아
✅ 셰난도아 수행 단계
셰난도아: 레드햇에서 제작한 자체 수집기
- 초기 마킹(STW)
- 동시 마킹
- 최종 마킹(STW)
- 동시 압착
✅ 브룩스 포인터
객체 당 메모리 워드를 하나 더 써서, 새 버전 객체 콘텐츠의 위치를 가리킨다.
<재배치 전>
재배치되지 않은 객체의 브룩스 포인터는 그냥 포인터 다음 워드를 가리킨다.
<재배치 후>
재배치가 되면, 기존에 있었던 위치에 브룩스 포인터를 남겨둔다.
이 때 기존 위치의 브룩스 포인터는 객체의 새 위치를 가리킨다.
-> 이렇게 함으로써, 기존 객체를 참조했던 다른 객체들은 브룩스 포인터를 통해 객체의 새 위치를 알 수 있게 된다.
동시 압착
✅ 압착 스레드 동작
G1과는 다르게 셰난도아는 압착을 애플리케이션 스레드와 동시에 진행
- 객체를 TLAB로 (추측하여) 복사한다.
- CAS(동시성 보장)로 브룩스 포인터가 추측성 사본을 가리키도록 수정한다.
- 이 작업이 성공하면 압착 스레드가 승리한 것으로, 이후 이 버전의 객체는 모두 브룩스 포인터를 경유해서 엑세스하게 된다.
- 이 작업이 실패하면 압착 스레드가 실패한 것으로, 추측성 사본을 원상복구하고 승리한 스레드가 남긴 브룩스 포인터를 따라간다.
셰난도아 얻기
✅ 셰난도아 스위치
-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의 영향을 배제한 테스트를 하기 위해서
마치며
✅ 여러 가비지 수집기의 트레이드오프
가비지 수집은 자바 성능 및 튜닝에서 정말 중요한 요소다.
가비지마다 트레이드오프와 성능을 꼼꼼하게 잘 따져,
애플리케이션에 적합한 수집기를 선택하자.
'book > 자바 최적화' 카테고리의 다른 글
[자바 최적화] JVM의 코드 실행 (0) | 2023.09.11 |
---|---|
[자바 최적화] GC 로깅, 모니터링, 튜닝, 툴 (0) | 2023.09.05 |
[자바 최적화] 가비지 수집 기초 (2) | 2023.08.28 |
[자바 최적화] 마이크로벤치마킹과 통계 (2) | 2023.08.23 |
[자바 최적화] 성능 테스트 패턴 및 안티패턴 (2) | 2023.08.10 |