2주차 Unit 4.5 — G1의 우선순위 기반 회수

Psj·2026년 5월 15일

F-lab

목록 보기
68/238

Unit 4.5 — G1의 우선순위 기반 회수

F-LAB JAVA · 2주차 · Phase 4 · G1 GC 심화
🏁 Phase 4 마지막 Unit — Garbage First의 정체


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • "Garbage First" 이름의 정확한 의미는?
  • 각 리전의 회수 효과 를 G1이 어떻게 계산하나?
  • 정지시간 한도 안에서 회수 리전을 어떻게 선택하나?
  • Concurrent Cycle 의 5단계는?
  • 모든 리전을 회수 안 해도 메모리가 안 부족한 이유는?
  • ILIC 운영의 GC 튜닝 마스터 가 됐는가?
  • Phase 4 졸업 — 운영 GC 마스터 가 됐는가?

🎯 핵심 한 문장

"Garbage First" = "쓰레기 많은 리전 먼저 처리"
G1은 모든 리전을 동등하게 회수하지 않는다.
각 리전의 garbage 비율 + 회수 시간을 계산해, 정지시간 한도 안에서 효과 큰 리전부터 선택.
이 우선순위 알고리즘이 G1을 G1답게 만드는 마지막 조각이다.

비유 — 청소 시간이 정해진 청소부

시스템비유
정지시간 200ms청소 시간 30분 제한
garbage 비율각 방의 더러운 정도
회수 시간각 방 청소에 걸리는 시간
Garbage First"가장 더럽고 빨리 청소되는 방부터"

청소부 전략:

  • 5분에 청소 가능한 매우 더러운 방 → 1순위
  • 10분에 청소 가능한 더러운 방 → 2순위
  • 1분 청소에 거의 깨끗한 방 → 우선순위 낮음

시간 대비 효과 가 기준.


🧭 9개 섹션 로드맵

1. Garbage First의 본질
2. 회수 효과 계산 방식
3. 정지시간 예측 모델 정밀화
4. Mixed Collection의 우선순위
5. Concurrent Cycle 전체 흐름
6. ILIC 실무 — GC 튜닝 종합 가이드
7. 운영 사고 디버깅 시나리오
8. 흔한 실수 + 디버깅
9. 면접 + Phase 4 졸업 시험

1️⃣ Garbage First의 본질

1.1 다른 GC와의 차이

Parallel GC:
  "Young 전체 회수" 또는 "Old 전체 회수"
  → 영역 단위 회수

CMS:
  "Old 전체 마킹 후 Sweep"
  → 영역 단위 + Concurrent

G1:
  "리전별 garbage 비율 계산"
  → "효과 큰 리전부터 선택적 회수"
  → 절대 모든 리전 한꺼번에 안 함

1.2 핵심 아이디어 — 시간 대비 효과

A 리전:
  - 크기: 2 MB
  - garbage: 1.8 MB (90%)
  - 회수 시간: 5 ms
  → 시간당 회수 효과: 360 MB/s

B 리전:
  - 크기: 2 MB
  - garbage: 0.4 MB (20%)
  - 회수 시간: 5 ms
  → 시간당 회수 효과: 80 MB/s

→ 같은 시간 쓰면 A 회수가 4.5배 효과적
→ A를 먼저 선택

1.3 G1의 회수 우선순위 알고리즘

1. 모든 Old 리전의 garbage 정보 수집
   (Concurrent Marking 단계에서)

2. 각 리전 정렬:
   효과 = garbage / 회수시간
   (높은 순)

3. 정지시간 한도 안에서 선택:
   while (시간 남음 && 후보 있음) {
     상위 후보 선택
     예상 시간 만큼 시간 차감
   }

4. 선택된 리전들만 회수

1.4 Young 리전은 어떤가

Young 리전은 다른 규칙:
  - Young GC에서 모두 회수
  - Eden 가득 차면 발동
  - 회수 효과 계산 없이 다 처리

Mixed GC:
  - Young + 선택된 Old 일부
  - Old만 Garbage First 알고리즘 적용

이유:

  • Young 리전은 어차피 가득 차면 처리해야 함
  • 거기서 우선순위 따질 필요 없음
  • Old는 "어디부터 처리할까"가 중요

1.5 자기 점검 답변

모든 리전을 다 회수하지 않아도 메모리가 부족해지지 않는 이유는?

:
1. 정지시간 한도 내에서 가장 효과적인 리전 선택
2. 한 번에 다 회수 안 해도, 점진적으로 누적
3. 매 Mixed GC마다 일부 Old 회수
4. Old gen 전체를 여러 사이클에 걸쳐 비움
5. 만약 정말 부족하면 → Full GC 발동 (최후 수단)

연속된 Mixed GC가 결국 Old 전체를 처리.
→ 단, 너무 빠르게 채워지면 따라가지 못해 Full GC.


2️⃣ 회수 효과 계산 방식

2.1 garbage 비율 계산 — Concurrent Marking

Concurrent Marking 단계:

1. GC Roots에서 시작
2. 도달 가능한 객체 마킹 (살아있음)
3. 각 리전의 통계 수집:
   - 마킹된 객체 크기 = live
   - 리전 크기 - live = garbage
   - garbage 비율 = garbage / 리전 크기

4. 각 리전에 "회수 효과 점수" 저장

2.2 회수 시간 예측 — 학습 기반

G1이 과거 GC 데이터 학습:
  - 리전 A 회수 시간: 8ms (5번 평균)
  - 리전 B 회수 시간: 12ms (5번 평균)
  - 리전 X 회수 시간: 50ms (1번)
  
새 GC 사이클 시:
  과거 평균을 기반으로 예측
  "이 리전 회수에 약 10ms 걸릴 것"

2.3 종합 점수 계산

리전의 종합 점수 = garbage 크기 / 예상 회수 시간

= 시간당 회수 가능한 garbage 양 (MB/s)

높은 점수 = 좋은 후보.

2.4 동적 적응

G1은 매 GC마다 학습:
  - 예측 시간 vs 실제 시간 비교
  - 차이가 크면 모델 조정
  - 점점 정확해짐

운영 초기:
  - 예측 부정확
  - 정지시간 변동 큼
  - 안정화까지 수 분~수 시간

운영 안정 후:
  - 예측 정확
  - 일관된 STW

→ 박승제씨가 ILIC 배포 직후 정지시간이 들쭉날쭉한 이유.
→ 시간 지나면 안정화.

2.5 리전 점수 시각화

예시 — Old 리전 10개 + 정지시간 한도 100ms:

Old 리전들:
  R1: garbage 90%, 예상 8ms → 점수 매우 높음 ★
  R2: garbage 80%, 예상 10ms → 높음
  R3: garbage 70%, 예상 12ms → 중간
  R4: garbage 60%, 예상 9ms → 중간
  R5: garbage 50%, 예상 15ms → 낮음
  R6: garbage 30%, 예상 11ms → 낮음
  R7: garbage 95%, 예상 30ms → 중간 (시간 김)
  R8: garbage 20%, 예상 7ms → 매우 낮음
  R9: garbage 85%, 예상 9ms → 높음 ★
  R10: garbage 75%, 예상 11ms → 높음

선택 (정지시간 한도 100ms):
  R1 (8ms) + R9 (9ms) + R2 (10ms) + R10 (11ms) 
  + R3 (12ms) + R4 (9ms) + R5 (15ms) + R7 (30ms - 안 됨)
  = 74ms 사용 ✓ 한도 안

선택 안 된 리전:
  R6, R7, R8 → 다음 Mixed GC로 미룸

3️⃣ 정지시간 예측 모델 정밀화

3.1 Unit 4.2 복습 + 깊이

사용자: "Pause 200ms 안에 끝내줘" (-XX:MaxGCPauseMillis=200)
G1: "최선 다해볼게"

내부 처리:
  1. 과거 GC 시간 학습
  2. 회수 후보 리전들의 점수 계산
  3. 한도 안에서 최대 효과 조합 선택
  4. 그것만 처리

3.2 사용자 한도 vs G1의 실제 처리

사용자 설정: MaxGCPauseMillis=200

G1의 처리:
  목표: < 200ms
  실제: 평균 50~150ms
  
  하지만:
  - Full GC 발생 시: 수 초 (목표 무관)
  - Humongous 처리 시: 한도 초과 가능
  - 매우 큰 root set: 한도 초과 가능

→ "목표"이지 "보장"은 아님.

3.3 한도가 너무 작으면

-XX:MaxGCPauseMillis=10   (10ms 목표)

문제:
  - 한 번에 회수할 수 있는 리전 수가 매우 적음
  - 자주 GC 발동
  - Old gen 회수 속도 느림
  - 결국 Old 가득 → Full GC

권장 범위: 100~500ms (대부분 200ms 적절)

3.4 한도가 너무 크면

-XX:MaxGCPauseMillis=2000   (2초 목표)

문제:
  - GC 시간 길어짐 (한 번에 많이 처리)
  - 응답 시간 폭증
  - SLA 위반
  - G1의 의미 없음 (Parallel GC와 비슷해짐)

3.5 적정 값 찾기

ILIC의 SLA에 따라:
  
SLA P99 < 500ms:
  → MaxGCPauseMillis=200
  
SLA P99 < 200ms:
  → MaxGCPauseMillis=100 + ZGC 검토

SLA P99 < 50ms:
  → G1 부족. ZGC/Shenandoah 권장

박승제씨가 ILIC SLA에 맞춰 조정.


4️⃣ Mixed Collection의 우선순위

4.1 Mixed Collection 트리거

Old gen 사용량 도달:
  ≥ -XX:InitiatingHeapOccupancyPercent=45 (기본 45%)
  
→ Concurrent Cycle 시작 (Marking)
→ Marking 완료 후 Mixed Collection 발동

4.2 Concurrent Cycle의 단계 (정밀)

1. Initial Mark (STW, 짧음)
   - GC Roots부터 시작점 마킹
   - Young GC와 동시 수행
   
2. Concurrent Mark (앱과 동시)
   - 그래프 탐색
   - 가장 오래 걸리는 단계
   - 앱은 정상 동작
   
3. Remark (STW, 짧음)
   - Concurrent 중 변경된 부분 처리
   - SATB(Snapshot-At-The-Beginning) 버퍼 처리
   
4. Cleanup (STW, 짧음)
   - 통계 수집 (각 리전의 garbage 비율)
   - 빈 리전 즉시 회수
   - 다음 Mixed GC 후보 리스트 생성
   
5. Mixed Collections (STW, 여러 사이클)
   - Young + 선택된 Old 리전
   - Garbage First 우선순위 적용
   - 후보 리스트 다 처리할 때까지

4.3 Mixed Collection의 횟수

하나의 Concurrent Cycle 후:
  - 후보 Old 리전: 100개 (예)
  - 한 번에 처리 가능: 10~20개 (정지시간 한도)
  - → 5~10번의 Mixed GC 사이클

진행:
  Mixed GC 1: 가장 효과 큰 20개 처리
  Mixed GC 2: 다음 20개
  Mixed GC 3: 다음 20개
  ...
  Mixed GC N: 마지막 처리
  
완료 후:
  Old gen 사용량 다시 떨어짐
  새 Concurrent Cycle 발동 시점까지 일반 Young GC

4.4 -XX:G1MixedGCCountTarget

한 Concurrent Cycle 후 Mixed GC 횟수 목표:

-XX:G1MixedGCCountTarget=8   (기본 8)

작게 하면:
  - 한 번에 많이 처리
  - 정지시간 길어짐
  - 빠른 Old 정리

크게 하면:
  - 한 번에 적게 처리
  - 정지시간 짧음
  - Old 정리 느림

4.5 -XX:G1MixedGCLiveThresholdPercent

Old 리전의 live 비율이 이 값 이하면 Mixed GC 대상:

-XX:G1MixedGCLiveThresholdPercent=85   (기본 85)

의미:
  - 리전의 85% 이상이 live = garbage 15% 이하
  - "그 정도면 회수해도 효과 적음" → 건드리지 않음
  - 다음 Concurrent Cycle까지 대기

→ G1이 "어차피 깨끗한 리전 회수에 시간 낭비 안 해"


5️⃣ Concurrent Cycle 전체 흐름

5.1 흐름 다이어그램

시간 →

┌──Initial Mark (STW 짧음)─┐
│ Young GC + Root 마킹     │
└──────────────────────────┘
                              ↓
                            ┌──Concurrent Mark (앱과 동시)──┐
                            │ 그래프 탐색 (긴 시간)          │
                            │ 앱: 정상 동작                  │
                            └────────────────────────────────┘
                                                    ↓
                                                  ┌──Remark (STW)─┐
                                                  │ SATB 처리       │
                                                  └─────────────────┘
                                                                ↓
                                                              ┌──Cleanup (STW)─┐
                                                              │ 통계 + 빈 리전  │
                                                              └────────────────┘
                                                                              ↓
                                                                            ┌──Mixed GCs─┐
                                                                            │ Young + Old │
                                                                            │ (8번 정도)  │
                                                                            └────────────┘

5.2 각 단계의 STW 비교

STW 시간 (4GB 힙 기준):
  - Initial Mark: ~5ms
  - Concurrent Mark: STW 없음 (앱과 동시)
  - Remark: ~10ms
  - Cleanup: ~5ms
  - Mixed GC 1번: ~50-100ms

총 STW (1 cycle):
  ~ 100ms (Initial + Remark + Cleanup)
  + Mixed GC 8회 × 80ms = 640ms
  
하지만 분산됨:
  - Initial Mark, Remark, Cleanup: 짧고 빈번
  - Mixed GC: 1초~분 간격
  - Concurrent Mark: 앱 영향 적음

5.3 SATB 메커니즘 정밀

SATB (Snapshot-At-The-Beginning):

Initial Mark 시점에 "객체 그래프 스냅샷" 작성
  ↓
Concurrent Mark 진행 중 앱이 참조 변경 시:
  - 변경 전 참조 정보를 SATB 버퍼에 기록
  - 변경은 정상 수행
  ↓
Remark 단계:
  - SATB 버퍼의 변경분 처리
  - 스냅샷 + 변경 반영해서 최종 마킹

→ Concurrent 중에도 앱 영향 최소.
→ G1의 핵심 메커니즘.

5.4 Write Barrier의 두 가지 역할

Phase 4.3에서 본 Write Barrier는 사실 두 가지 일:

1. Card Table 마킹 (RSet용)
   - 리전 경계 넘는 참조 → 카드 dirty

2. SATB 버퍼 기록 (Concurrent용)
   - Concurrent Mark 중 → 변경 전 참조 기록

→ 일반 코드 실행 시마다 ~2-5% 오버헤드.

5.5 Mixed GC 실패 — Concurrent Mode Failure

시나리오:
  Concurrent Marking 중
  앱이 너무 빨리 객체 생성
  Old gen이 가득 참
  
결과:
  to-space exhausted
  Full GC 발동 (Serial 또는 Parallel)
  매우 긴 STW (수 초)

해결:

# 더 일찍 Concurrent 시작
-XX:InitiatingHeapOccupancyPercent=30   (기본 4530)

# 예비 공간 늘림
-XX:G1ReservePercent=15   (기본 10)

# Heap 크기 증가
-Xmx8g   (4g → 8g)

6️⃣ ILIC 실무 — GC 튜닝 종합 가이드

6.1 단계별 튜닝 절차

1단계: 기본 옵션 사용 (G1 자동)
  -XX:+UseG1GC
  -Xms4g -Xmx4g
  
2단계: 모니터링 (1-2주)
  - GC 로그 분석
  - P99 응답 시간
  - Full GC 빈도
  
3단계: 필요 시 튜닝
  - 정지시간 목표 조정
  - Concurrent 시작 시점 조정
  - 리전 크기 조정
  
4단계: 부하 테스트
  - 변경 후 효과 검증
  - SLA 만족 여부

6.2 운영 옵션 종합 세트 (G1)

# === 메모리 ===
-Xms4g                                        # 초기 = 최대
-Xmx4g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

# === G1 GC ===
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200                      # 정지시간 목표

# (선택, 기본 OK)
-XX:G1HeapRegionSize=8m                       # 리전 크기 (자동 권장)
-XX:InitiatingHeapOccupancyPercent=45         # Concurrent 시작 임계치
-XX:G1MixedGCCountTarget=8                    # Mixed GC 횟수 목표
-XX:G1MixedGCLiveThresholdPercent=85          # Old 리전 처리 임계치
-XX:G1ReservePercent=10                       # 예비 공간

# === GC 로깅 (Java 17+) ===
-Xlog:gc*:file=/var/log/gc-%t.log:time,uptime,level,tags:filecount=10,filesize=100M

# === OOM 대응 ===
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heap-dumps/
-XX:OnOutOfMemoryError="kill -9 %p"

# === JFR (선택) ===
-XX:StartFlightRecording=disk=true,maxsize=1g,maxage=24h,filename=/var/log/jfr/app.jfr

6.3 시나리오별 옵션 가이드

시나리오 A — 안정적 SLA (P99 500ms)

-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

기본만으로 충분.

시나리오 B — Full GC 발생 중

# Concurrent를 일찍 시작
-XX:InitiatingHeapOccupancyPercent=30
# 예비 공간 늘림
-XX:G1ReservePercent=15
# Heap 증가
-Xms8g -Xmx8g

시나리오 C — Humongous 빈번

# 리전 크기 증가
-XX:G1HeapRegionSize=32m

또는 코드 수정 (Unit 4.4).

시나리오 D — 매우 엄격한 SLA (P99 50ms)

# ZGC로 전환
-XX:+UseZGC
-Xms16g -Xmx16g

6.4 GC 로그 분석 워크플로우

# 1. 로그 수집 (1일치)
cp /var/log/gc-*.log analysis/

# 2. 도구 분석
# GCViewer
java -jar gcviewer-1.36.jar analysis/gc.log

# GCEasy (웹)
# https://gceasy.io 에 업로드

# 3. 분석 지표 확인
# - Throughput (앱 시간 / 전체 시간)
# - Average / P99 GC pause
# - Full GC count
# - Memory pool usage trends

6.5 ILIC 운영 정기 체크리스트

일일 점검:
  ☐ Full GC 발생 횟수 (0이어야 함)
  ☐ 평균 Young GC pause (~ 50ms)
  ☐ P99 GC pause (~ 200ms)
  ☐ Concurrent Mode Failure 여부

주간 점검:
  ☐ Old gen 사용률 추세
  ☐ Mixed GC 횟수 정상
  ☐ Humongous Allocation 빈도
  ☐ Metaspace 사용률

월간 점검:
  ☐ JVM 옵션 재검토
  ☐ Heap 크기 적정성
  ☐ GC 알고리즘 적정성 (G1 vs ZGC)
  ☐ JDK 버전 업데이트 검토

7️⃣ 운영 사고 디버깅 시나리오

7.1 시나리오 1 — 갑작스러운 응답 지연

14:30 알림:
  P99 응답시간 200ms → 1500ms
  
1단계: GC 로그
  tail -100 /var/log/gc-*.log | grep "Pause"
  
  결과:
  GC(456) Pause Full (G1 Evacuation Pause) 3500.123ms
                                          ↑ Full GC!
  
2단계: Heap 상태
  jcmd <PID> GC.heap_info
  
  결과:
  Old gen 사용률: 95%   ← 거의 가득
  Humongous regions: 50   ← 거대 객체 많음
  
3단계: Heap dump
  jmap -dump:format=b,file=/tmp/heap.hprof <PID>
  
4단계: MAT 분석
  - Histogram: byte[] 가 50% 차지
  - Path to GC Roots: ImageCache.cache
  
5단계: 원인 식별
  - 이미지 캐시가 메모리에 5MB씩 100장 보관
  - Humongous 누적
  - Concurrent Cycle 따라가지 못함
  - 결국 Full GC
  
6단계: 해결
  - 이미지 캐시를 Caffeine + 크기 제한
  - 또는 외부 스토리지 (S3, Redis)
  
7단계: 재배포 + 검증

7.2 시나리오 2 — 점진적 성능 저하

배포 후 시간 흐름:
  Day 1: 평균 50ms
  Day 3: 평균 80ms
  Day 7: 평균 120ms
  
원인 추정: 메모리 누수
  
1단계: GC 로그 추세
  awk로 GC pause 시간 추출 → 그래프
  - Full GC 빈도 점점 증가
  
2단계: Heap 모니터링
  Old gen 사용률이 시간이 갈수록 회복 안 됨
  
3단계: Heap dump (현재 + 시작 시)
  비교 분석
  - 어떤 객체가 증가하고 있는지
  - GC 못 시키는 이유
  
4단계: 코드 리뷰
  - static 컬렉션? (Unit 4.1)
  - ThreadLocal 미정리?
  - Listener 미해제?
  - 캐시 만료 정책 없음?

5단계: 수정 후 재배포

7.3 시나리오 3 — Humongous Allocation 폭발

GC 로그 패턴:
  매 분마다 G1 Humongous Allocation
  → Mixed GC 부담 증가
  
원인 추적:
  - 새 기능 배포 시점부터 발생
  - PDF 생성 기능 추가
  
코드 확인:
  byte[] pdfBytes = pdfGenerator.generate(report);  // 5MB
  return ResponseEntity.ok(pdfBytes);
  
문제:
  매 요청마다 5MB byte[] 거대 객체
  요청 100건/초 = 초당 500MB Humongous 할당!
  
해결:
  스트리밍 응답으로 변경
  byte[] 메모리 적재 X

7.4 시나리오 4 — Concurrent Mode Failure

GC 로그:
  GC(789) To-space exhausted
  GC(789) Pause Full (Allocation Failure) 4500ms
  
원인:
  - Mixed GC가 Old 채우는 속도 못 따라감
  - 또는 evacuation 중 빈 리전 부족
  
해결 옵션:
  1. -XX:InitiatingHeapOccupancyPercent=30  (45 → 30)
  2. -XX:G1ReservePercent=15  (10 → 15)
  3. Heap 크기 증가
  4. 코드 최적화 (객체 생성 줄이기)

7.5 디버깅 도구 종합

# 실시간 GC 통계
jstat -gc <PID> 1000

# 출력:
# S0C   S1C    S0U    S1U    EC      EU      OC      OU      MC      MU
# 16384 16384  0.0    13800  131072  98456   262144  142000  77824   76341

# Heap 정보
jcmd <PID> GC.heap_info

# 메모리 풀별 사용량
jcmd <PID> VM.native_memory summary

# Class 통계
jcmd <PID> GC.class_histogram

# JFR 시작
jcmd <PID> JFR.start duration=60s filename=app.jfr

# Heap dump
jmap -dump:live,format=b,file=heap.hprof <PID>

7.6 ILIC 운영 자동화

# Prometheus 알림 규칙 예시
groups:
  - name: jvm_gc
    rules:
      - alert: FullGCDetected
        expr: increase(jvm_gc_collection_seconds_count{gc="G1 Old Generation"}[5m]) > 0
        annotations:
          summary: "Full GC 발생 감지"
      
      - alert: GCPauseHigh
        expr: rate(jvm_gc_pause_seconds_sum[5m]) > 0.5
        annotations:
          summary: "GC 시간 비중 50% 초과"
      
      - alert: OldGenHigh
        expr: jvm_memory_used_bytes{area="heap", id="G1 Old Gen"} / jvm_memory_max_bytes{area="heap", id="G1 Old Gen"} > 0.8
        annotations:
          summary: "Old gen 사용률 80% 초과"

8️⃣ 흔한 실수 + 디버깅

실수 1 — MaxGCPauseMillis 무리 설정

# ❌ 10ms 목표
-XX:MaxGCPauseMillis=10

# 결과:
# - 한 번에 회수 못 함
# - 자주 GC 발동
# - 결국 Full GC

→ 100-500ms가 현실적. 10ms 원하면 ZGC.

실수 2 — Concurrent 시작 너무 늦게

# ❌ 70%까지 기다림
-XX:InitiatingHeapOccupancyPercent=70

# 결과:
# - Concurrent Marking이 늦게 시작
# - Marking 중 Old 가득 → Concurrent Mode Failure

→ 기본 45% 유지. Full GC 자주면 30%로.

실수 3 — GC 옵션 마구 추가

# ❌ 인터넷에서 본 옵션 다 적용
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=4m \
-XX:G1MixedGCCountTarget=4 \
-XX:G1MixedGCLiveThresholdPercent=70 \
-XX:G1OldCSetRegionThresholdPercent=20 \
-XX:G1HeapWastePercent=5 \
... (수십 개)

옵션끼리 충돌하거나 불필요한 제약 발생.

→ 진짜 필요한 옵션만. 한 번에 하나씩 변경.

실수 4 — 부하 테스트 없이 변경

"이 옵션 적용했는데 운영 어때?"

❌ 운영에 바로 적용
✓ Stage에서 부하 테스트 → 측정 → 적용

실수 5 — GC 로그 안 봄

"GC가 문제인지 모르겠어"

→ 로그를 봐야 알지!

-Xlog:gc*:file=gc.log

운영 환경에 GC 로그 안 켜는 건 큰 실수.

실수 6 — Heap dump 분석 안 함

"메모리 누수가 의심돼"
"Heap dump는 어렵네"
"그냥 재시작"

→ 매번 재시작은 임시 해결.
→ 진짜 원인은 누군가 분석해야 함.

MAT, JProfiler 등 도구 익히기.


9️⃣ 면접 + Phase 4 졸업 시험

9.1 면접 단골 질문 매핑

Q핵심 답변
Garbage First 이름의 의미?쓰레기 많은 리전부터 회수
회수 효과 점수?garbage / 회수시간
정지시간 한도 보장?목표일 뿐, Full GC 등에선 깨짐
MaxGCPauseMillis 적정 값?100-500ms. ILIC는 200ms 권장
Concurrent Cycle 5단계?Initial Mark, Concurrent Mark, Remark, Cleanup, Mixed GCs
SATB의 역할?Concurrent Mark 중 객체 그래프 스냅샷
InitiatingHeapOccupancyPercent?Concurrent 시작 임계치 (기본 45%)
Mixed GC 횟수?G1MixedGCCountTarget=8 (기본)
Concurrent Mode Failure?Concurrent 속도 못 따라가서 Full GC
GC 튜닝 우선순위?모니터링 → 측정 → 한 번에 하나씩

9.2 자기 점검 체크리스트

기본 이해

  • Garbage First 알고리즘을 설명할 수 있다
  • 회수 효과 계산을 안다 (garbage / 시간)
  • Concurrent Cycle 5단계를 안다
  • SATB와 Card Table의 협력을 안다
  • Mixed GC와 Concurrent Mode Failure를 안다

실전 적용

  • G1 GC 옵션을 적절히 설정할 수 있다
  • GC 로그를 분석할 수 있다
  • Full GC 원인을 추적할 수 있다
  • Heap dump로 누수 식별 가능
  • Prometheus 알림 설정 가능

면접 대비 — 5분 답변

  • G1의 동작 원리 (Phase 4 전체)
  • GC 튜닝 절차
  • 운영 사고 디버깅
  • G1 vs ZGC 선택 기준
  • 메모리 누수 진단

9.3 🏆 Phase 4 졸업 시험

다음 질문에 즉답할 수 있다면 Phase 4 졸업:

  1. JVM이 참조 카운팅을 안 쓰는 이유와 Reachability의 동작은?
  2. G1 GC의 3가지 혁신은? (리전, 점진, 예측)
  3. RSet과 Card Table의 협력 메커니즘은?
  4. 거대 객체가 G1 효율에 미치는 영향 5가지는?
  5. ILIC 서버에서 Full GC 발생 시 디버깅 순서는?

모두 답할 수 있다면 Phase 4 완주. 운영 GC 마스터.


🎯 핵심 요약 — 3줄 정리

1. Garbage First = "효과 큰 리전 먼저"

  • 점수 = garbage / 회수시간
  • 정지시간 한도 안에서 최대 효과 조합 선택
  • 모든 리전 한꺼번에 안 함 → 점진 처리

2. Concurrent Cycle 5단계

  • Initial Mark (STW) → Concurrent Mark (동시) → Remark (STW) → Cleanup (STW) → Mixed GCs
  • SATB로 Concurrent 중 변경 추적
  • Mixed GC 8회 정도에 걸쳐 Old 정리

3. ILIC 운영 GC 마스터

  • 모니터링 → 측정 → 튜닝 (순서)
  • 기본 G1 + 적절한 옵션이 99% 충분
  • Full GC = 즉시 진단 + 코드 수정

🏆 Phase 4 완주 — 운영 GC 마스터 달성

🚀 Phase 4 — G1 GC 심화
  ✅ Unit 4.1 참조 카운팅의 한계
  ✅ Unit 4.2 G1 GC의 등장 배경
  ✅ Unit 4.3 리전 기반 레이아웃
  ✅ Unit 4.4 거대 리전
  ✅ Unit 4.5 우선순위 기반 회수 ← 여기, Phase 4 완주

→ 박승제씨는 이제 ILIC 운영 GC 마스터

Phase 4 후 박승제씨가 가진 능력

  • ✓ ILIC 서버 JVM 옵션을 정확히 설정
  • ✓ GC 로그를 일상적으로 모니터링
  • ✓ Full GC 발생 시 30분 내 원인 진단
  • ✓ Heap dump를 MAT로 분석
  • ✓ Humongous Allocation 패턴 코드에서 식별
  • ✓ G1 vs ZGC 선택 의사결정
  • ✓ Prometheus 기반 GC 알림 자동화

📚 다음으로...

Phase 5 — 컬렉션 프레임워크 내부 구조

1주차에서 컬렉션을 큰 그림으로 봤다면, 2주차 Phase 5는:

  • ArrayList 내부 (배열 확장 1.5배 정책)
  • LinkedList 내부 (노드 연결)
  • HashMap 내부 심화 (1주차 Unit 6에서 PPT로 본 것의 종합)
  • TreeMap, LinkedHashMap의 구조
  • 자료구조 선택의 정확한 기준

→ Phase 4가 운영 실무였다면, Phase 5는 코드 실무 직결.

2주차 진행 상황

✅ Phase 1 — 자바 변수 ↔ 메모리 매핑 (1.1 ~ 1.6 완주)
✅ Phase 2 — JVM 메서드 실행 메커니즘 (2.1 ~ 2.4 완주)
✅ Phase 3 — 바이트코드와 상수 풀 (3.1 ~ 3.4 완주, 정점)
✅ Phase 4 — G1 GC 심화 (4.1 ~ 4.5 완주, 운영 마스터)
🚀 Phase 5 — 컬렉션 내부 구조 (다음)
⏭ Phase 6 — Reflection & Iterator
⏭ Phase 7 — Buffer

작성한 2주차 학습자료

Phase 1: 6개 Unit (1.1 ~ 1.6)
Phase 2: 4개 Unit (2.1 ~ 2.4)
Phase 3: 4개 Unit (3.1 ~ 3.4) ★ 정점
Phase 4: 5개 Unit (4.1 ~ 4.5) — 운영 마스터
─────────────────────────────
누적: 19개 Unit

2주차의 약 80% 완주
profile
Software Developer

0개의 댓글