이 글에서 얻는 것
- 객체 할당이 실제로 어떤 경로로 빠르게 수행되는지(TLAB) 설명할 수 있습니다.
- Card Table/Remembered Set이 왜 필요한지, Minor GC 스캔 범위를 어떻게 줄이는지 이해합니다.
- GC가 사용하는 Write/Read Barrier의 역할을 인지하고, 성능에 미치는 영향을 감 잡을 수 있습니다.
1) 객체 할당 경로: TLAB와 bump-pointer
JVM에서 객체 할당이 느리면, 애플리케이션 전체가 느려집니다. 그래서 대부분의 할당은 락 없는 빠른 경로를 탑니다.
1-1. TLAB 개념
- 각 스레드가 자기 전용 버퍼(TLAB) 를 받아 동기화 없이 객체를 할당합니다.
- 할당은 bump-pointer(포인터를 앞으로 이동) 한 번이면 끝입니다.
// 개념적 표현 (실제 JVM 내부는 C++ 구현)
Object allocate(int size) {
if (tlab.remaining() >= size) {
Object obj = tlab.bump(size);
return obj;
}
return slowPathAllocate(size); // TLAB 부족 -> 전역 힙에서 재할당
}
1-2. 왜 중요한가?
- 멀티스레드에서 할당 시 락 경합이 사라짐
- 작은 객체를 대량 생성하는 환경(웹/배치)에서 성능 차이가 크게 발생
실무 팁: TLAB 부족으로 slow path가 잦다면, 객체 할당 패턴이나 힙 크기/GC 설정을 의심해볼 수 있습니다.
2) Card Table: Old -> Young 참조 추적의 핵심
Minor GC는 Young 영역만 정리합니다. 그런데 Old 영역에서 Young을 참조하고 있으면, 그 Young 객체는 살아있어야 합니다.
문제: Old 전체를 스캔하면 너무 느립니다. 해결: Card Table로 변경된 영역만 추적합니다.
2-1. Card Table 동작
- 힙을 작은 카드(예: 512B~1KB) 단위로 나눕니다.
- Old 객체의 참조가 변경될 때, 해당 카드에 dirty 표시를 합니다.
- Minor GC에서는 dirty 카드만 스캔하면 됩니다.
[Heap]
| Card0 | Card1 | Card2 | Card3 | ... |
clean dirty clean dirty
Minor GC -> Card1, Card3만 스캔
3) Remembered Set (RSet): G1의 지역적 스캔
G1은 힙을 여러 Region으로 쪼개고, 각 Region마다 외부에서 들어오는 참조 집합(RSet) 을 유지합니다.
- Region A를 수집할 때, A를 참조하는 다른 Region만 확인하면 됨
- “전체 스캔”이 아니라 “지역 스캔”으로 비용 절감
flowchart LR
R1[Region 1] -->|ref| R2[Region 2]
R3[Region 3] -->|ref| R2
R2 -->|RSet| R1 & R3
4) Write Barrier / Read Barrier: GC의 신경망
4-1. Write Barrier
- 참조 필드가 변경되는 순간에 실행되는 짧은 코드
- Card Table/RSet을 갱신해 GC가 “어디를 봐야 하는지” 알게 함
// 개념적 의사코드
void writeField(Object obj, Object newRef) {
obj.field = newRef;
CardTable.markDirty(obj); // Write Barrier
}
4-2. Read Barrier
- 일부 동시성 GC(ZGC, Shenandoah 등)에서 사용
- 읽기 시점에 객체 상태를 확인/보정하여 STW를 최소화
이해 포인트: Barriers는 “GC 비용을 런타임에 분산”시키는 장치입니다.
5) GC 내부 구조가 실무에 주는 힌트
- 할당 폭주 = GC 빈도 증가 → 객체 생성 줄이기 / 풀링 고려
- Old -> Young 참조가 많아지면 → Minor GC 비용 증가 가능
- 동시성 GC의 Barrier 비용 → 극저지연 시스템에서 읽기/쓰기 비용을 고려해야 함
요약
- TLAB: 스레드별 빠른 할당 경로로 성능을 결정한다.
- Card Table: Old -> Young 참조를 빠르게 추적해 Minor GC 비용을 줄인다.
- RSet: G1의 Region 단위 스캔을 가능하게 한다.
- Write/Read Barrier: GC 비용을 런타임에 분산시키는 핵심 메커니즘이다.
연습(추천)
-XX:+PrintTLAB옵션으로 TLAB 할당 통계를 확인해보기- G1에서
-Xlog:gc+remset=debug로 RSet 갱신 로그를 살펴보기 - 객체 참조 패턴을 바꿔가며 Minor GC 시간 변화 관찰하기
💬 댓글