본 내용은 JVM 밑바닥까지 파헤치기 책을 읽으며 공부한 내용입니다.
출처: https://www.yes24.com/product/goods/126114513
JVM 밑바닥까지 파헤치기 - 예스24
“자바 가상 머신의 깊숙한 내부를 향해 떠나는 흥미진진한 모험”C·C++를 사용해 주로 프로그래밍을 하던 시절 까다로운 메모리 관리와 플랫폼 이식성 문제는 개발자들에게 적지 않은 부담이
www.yes24.com
"Stop-The-World를 최소화한 최초의 동시성 GC" - CMS가 가져온 혁신과 한계를 파헤쳐보자
JVM의 가비지 컬렉션 역사에서 CMS(Concurrent Mark Sweep) 컬렉터는 특별한 의미를 가집니다. JDK 5에서 등장한 CMS는 최초로 진정한 동시성을 지원하는 GC로, 애플리케이션의 응답 시간을 혁신적으로 개선했습니다. 비록 JDK 14에서 완전히 제거되었지만, 현대 GC들의 아버지 역할을 한 CMS의 모든 것을 알아보겠습니다.
🚀 CMS의 혁신: 동시성의 시작
CMS 이전의 세상
CMS 이전의 GC들은 모두 Stop-The-World 방식이었습니다:
전통적인 GC 동작:
[App] [App] [App] → [STOP] [GC] [GC] [GC] → [App] [App] [App]
↑ ↑
모든 스레드 정지 재개
CMS의 혁신
CMS는 GC와 애플리케이션이 동시에 실행되는 새로운 패러다임을 제시했습니다:
CMS GC 동작:
[App] [App] [App] [App] [App] [App] [App] [App]
↓ ↓ ↓ ↓
[GC] [GC] [GC] [GC]
동시 실행 동시 실행 동시 실행
🔍 CMS의 4단계 동작 과정
CMS는 마크-스윕 알고리즘을 기반으로 4단계로 동작합니다.
1단계: 최초 표시 (Initial Mark)
// CMS 활성화
java -XX:+UseConcMarkSweepGC MyApplication
특징:
- 짧은 Stop-The-World 발생
- GC 루트와 직접 연결된 객체들만 표시
- 매우 빠르게 완료 (보통 수 밀리초)
Initial Mark 과정:
GC Root → [Object A] ✓
GC Root → [Object B] ✓
GC Root → [Object C] ✓
표시된 객체: A, B, C (직접 참조만)
소요 시간: 2-5ms (Stop-The-World)
2단계: 동시 표시 (Concurrent Mark)
특징:
- 사용자 스레드와 동시 실행
- GC 루트에서 객체 그래프 전체를 탐색
- 가장 시간이 오래 걸리는 단계
Concurrent Mark 과정:
[App Thread 1] [App Thread 2] [App Thread 3] ← 계속 실행
[GC Thread] ← 동시에 객체 그래프 탐색
Object A → Object D ✓ → Object G ✓
Object B → Object E ✓ → Object H ✓
Object C → Object F ✓ → Object I ✓
소요 시간: 수백ms ~ 수초 (동시 실행)
3단계: 재표시 (Remark)
특징:
- 짧은 Stop-The-World 발생
- 동시 표시 중 변경된 참조 관계를 수정
- 증분 업데이트(Incremental Update) 기법 사용
// 재표시 단계 최적화 옵션
java -XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled // 재표시를 병렬로 수행
MyApplication
Remark 과정:
동시 표시 중 발생한 변경사항:
- Object A → Object X (새로 생성된 참조)
- Object B → null (참조 해제)
이런 변경사항들을 빠르게 수정
소요 시간: 10-50ms (Stop-The-World)
4단계: 동시 쓸기 (Concurrent Sweep)
특징:
- 사용자 스레드와 동시 실행
- 죽은 객체들의 메모리를 회수
- 메모리 단편화 발생 가능
Concurrent Sweep 과정:
[App Thread 1] [App Thread 2] [App Thread 3] ← 계속 실행
[GC Thread] ← 동시에 죽은 객체 메모리 회수
Before: [Live][Dead][Live][Dead][Live][Dead]
After: [Live][Free][Live][Free][Live][Free]
소요 시간: 수십ms ~ 수백ms (동시 실행)
📊 CMS 성능 분석
실제 성능 비교
테스트 환경: 4코어, 4GB 힙, 웹 애플리케이션
Parallel GC vs CMS GC 비교:
Parallel GC:
- Young GC: 50ms (Stop-The-World)
- Full GC: 2000ms (Stop-The-World)
- 평균 응답시간: 150ms
- 최대 응답시간: 2050ms
CMS GC:
- Young GC: 30ms (ParNew 사용)
- CMS GC: 5ms + 500ms(동시) + 15ms + 200ms(동시)
- 평균 응답시간: 80ms
- 최대 응답시간: 50ms
CMS 튜닝 옵션
# CMS 기본 설정
java -XX:+UseConcMarkSweepGC \
-XX:+CMSParallelRemarkEnabled \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:+UseCMSInitiatingOccupancyOnly \
MyApplication
주요 튜닝 매개변수:
// CMS 시작 시점 조절
-XX:CMSInitiatingOccupancyFraction=70 // 구세대 70% 사용 시 CMS 시작
// 재표시 단계 최적화
-XX:+CMSParallelRemarkEnabled // 병렬 재표시
// 증분 모드 (구버전에서 사용)
-XX:+CMSIncrementalMode // 점진적 CMS (JDK 8에서 제거)
// 메모리 단편화 해결
-XX:+UseCMSCompactAtFullCollection // Full GC 시 압축 수행
-XX:CMSFullGCsBeforeCompaction=5 // 5번의 Full GC 후 압축
⚠️ CMS의 한계와 문제점
1. 프로세서 자원에 민감
CMS는 CPU 집약적입니다:
CPU 사용률 비교 (4코어 시스템):
일반 GC 동작 시:
App: 0% | GC: 100% (1코어) | 유휴: 75%
CMS 동작 시:
App: 75% | GC: 25% (1코어) | 유휴: 0%
문제점:
- 동시 실행으로 인한 애플리케이션 성능 저하
- 전체 처리량 감소
- CPU 코어가 적을수록 영향 심각
2. 부유 쓰레기 (Floating Garbage)
동시 실행 중 생성되는 처리되지 못한 쓰레기:
public class FloatingGarbageExample {
public void demonstrateFloatingGarbage() {
// CMS 동시 표시 단계 중...
Object temp = new Object(); // 새로 생성된 객체
// 동시 표시가 이 객체를 지나간 후
temp = null; // 참조 해제
// 이 객체는 다음 GC까지 메모리에 남아있음 (부유 쓰레기)
}
}
부유 쓰레기로 인한 문제:
- 메모리 사용량 증가
- 동시 모드 실패 위험 증가
- 예측하기 어려운 메모리 패턴
3. 동시 모드 실패 (Concurrent Mode Failure)
가장 심각한 CMS의 문제점:
동시 모드 실패 시나리오:
1. CMS 동시 수집 진행 중 (구세대 80% 사용)
2. 애플리케이션이 빠르게 객체 생성
3. 구세대 공간 부족 발생
4. CMS 중단, Serial Old로 전환
5. 긴 Stop-The-World 발생 (수 초)
# 동시 모드 실패 로그 예시
[GC [1 CMS-initial-mark: 524288K(1048576K)] 524288K(1572864K), 0.0023781 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 2.034/2.034 secs]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.015/0.015 secs]
[GC [1 CMS-remark: 524288K(1048576K)] 786432K(1572864K), 0.0234567 secs]
[CMS-concurrent-sweep-start]
[Full GC (Concurrent Mode Failure) 1048576K->262144K(1048576K), 3.2345678 secs]
↑ 심각한 성능 저하!
4. 메모리 단편화
마크-스윕 알고리즘의 근본적 한계:
메모리 단편화 예시:
Before Sweep:
[Live 100K][Dead 50K][Live 200K][Dead 30K][Live 150K][Dead 70K]
After Sweep:
[Live 100K][Free 50K][Live 200K][Free 30K][Live 150K][Free 70K]
문제: 120K 객체를 할당하려 해도 연속된 공간이 없음!
🛠️ CMS 실무 활용 가이드
적합한 사용 사례
CMS가 적합한 애플리케이션:
✅ 웹 애플리케이션:
- 낮은 지연 시간 요구
- 사용자 응답성 중요
- 중간 규모의 힙 (2-8GB)
✅ 실시간 시스템:
- 일정한 응답 시간 보장 필요
- Stop-The-World 시간 최소화 요구
❌ 부적합한 사례:
- 배치 처리 시스템 (처리량 중심)
- 매우 큰 힙 (>8GB)
- CPU 코어가 적은 환경 (<4코어)
JVM 밑바닥까지 파헤치기 - 예스24
“자바 가상 머신의 깊숙한 내부를 향해 떠나는 흥미진진한 모험”C·C++를 사용해 주로 프로그래밍을 하던 시절 까다로운 메모리 관리와 플랫폼 이식성 문제는 개발자들에게 적지 않은 부담이
www.yes24.com
'개발 > JVM' 카테고리의 다른 글
[JVM 밑바닥까지 파헤치기] 저지연 가비지 컬렉터 - 셰넌도어 & ZGC의 혁신 ⚡ (0) | 2025.07.07 |
---|---|
[JVM 밑바닥까지 파헤치기] G1GC 완벽 분석 - 부분회수와 리전으로 새로운 패러다임 🎯 (2) | 2025.07.05 |
[JVM 밑바닥까지 파헤치기] JVM 클래식 가비지 컬렉터 완전 정복 🗑️ (0) | 2025.07.03 |
[JVM 밑바닥까지 파헤치기] 가비지 컬렉션 알고리즘 완벽분석 (0) | 2025.07.01 |
[JVM 밑바닥까지 파헤치기] 가비지 컬렉터의 객체 생존 여부 판단 방식 🚽 (0) | 2025.06.30 |