전체 목록
JavaHard#052

Java의 가비지 컬렉션(GC) 동작 원리와 주요 GC 알고리즘을 설명해주세요.

#Java#GC#JVM#성능

답변 포인트

Minor GC / Major GC, Stop-the-World, G1 GC를 생각해보세요.

정답 및 해설

빠른 요약

GC는 더 이상 참조되지 않는 Heap 객체를 자동으로 회수합니다. 동작 흐름: 1) 객체는 Eden에 생성 → Minor GC 때 살아남으면 Survivor로 이동 2) 여러 번 살아남으면 Old 영역으로 승격(Promotion) 3) Old 영역이 꽉 차면 Major(Full) GC 실행 Stop-the-World: GC 실행 중 모든 애플리케이션 스레드가 멈춤.

가비지 컬렉션(GC)은 JVM이 더 이상 사용되지 않는 객체를 자동으로 메모리에서 해제하는 메커니즘입니다. 개발자가 직접 메모리를 관리하지 않아도 되어 메모리 누수 위험이 줄어들지만, GC가 실행되는 동안 애플리케이션이 일시적으로 멈추는 "Stop-the-World" 현상이 발생할 수 있습니다. GC의 동작 원리를 이해하면 성능 튜닝과 메모리 이슈 해결에 크게 도움이 됩니다.

GC의 기본 원리

객체 생존 여부 판단

Java
// GC는 "도달 가능성(Reachability)"으로 객체 생존 여부 판단
// GC Root에서 시작하여 참조를 따라갈 수 있으면 생존(Reachable)
// 참조를 따라갈 수 없으면 수거 대상(Unreachable)

// GC Root가 되는 것들:
// 1. JVM Stack의 지역 변수
// 2. Method Area의 static 변수
// 3. JNI에 의해 생성된 참조

void example() {
  // GC Root: 지역 변수
  User user1 = new User("김철수")  // user1 → User 객체 (Reachable)
  User user2 = new User("이영희")  // user2 → User 객체 (Reachable)

  user2 = user1  // 이영희 User 객체 → 참조 없음 → Unreachable → GC 대상

  user1 = null   // 김철수 User 객체 → 참조 없음 → GC 대상
  // 두 객체 모두 GC 대상이 됨
}

// 순환 참조도 GC가 수거 가능 (Reference Counting 방식은 불가능하지만
// Java의 Mark-and-Sweep 방식은 순환 참조도 처리 가능)
class Node {
  Node next
}
Node a = new Node()
Node b = new Node()
a.next = b  // a → b
b.next = a  // b → a (순환 참조)
a = null
b = null    // GC Root에서 도달 불가 → 순환 참조임에도 GC 대상

GC 동작 흐름 (Generational GC)

세대(Generation) 가설

Java
   (Weak Generational Hypothesis)
       
   GC  

단계별 동작

Java
1.    Eden Space 
   
    Eden: [A][B][C][D][E][F][G]   (  )
    S0:   []                     
    S1:   []                     
   

2. Eden    Minor GC(Young GC) 
   - Mark:    (A, C, E, G )
   - Copy:   S0 , age = 1
   
    Eden: []                       ()
    S0:   [A(1)][C(1)][E(1)][G(1)] 
    S1:   []                     
   

3.  Eden    Minor GC 
   - Eden S0   S1 , age 
   
    Eden: []                           
    S0:   []                           
    S1:   [A(2)][E(2)][H(1)][I(1)]    (age  )
   

4. age ( 15)   Old Generation (Promotion)
   - A age=15   Old Gen 

5. Old Gen    Major GC(Full GC) 
   -  , Stop-the-World   

Minor GC vs Major GC

Java
Minor GC (Young GC):
- Young Generation 
-   ( ~  )
- Stop-the-World:  ( ms ~  ms)
-    

Major GC (Full GC):
- Old Generation +  Heap 
-  
- Stop-the-World:    ( ms ~  )
-      GC  

GC 알고리즘

Serial GC

Bash
# 사용 설정
-XX:+UseSerialGC

:
-   GC 
- GC      (Stop-the-World)
- Young: Mark-Copy, Old: Mark-Sweep-Compact

# 사용 적합한 경우:
# - 단일 코어 CPU 환경
# - 메모리가 적은 임베디드 환경
# - 클라이언트 사이드 애플리케이션
Java
Serial GC :
Single Thread GC

 GC Thread:    (GC  )
 App Thread:      ( )

Parallel GC

Bash
# 사용 설정 (Java 8 기본값)
-XX:+UseParallelGC
-XX:ParallelGCThreads=4  # GC 스레드 수

:
-    GC 
- Young Generation  
- Throughput  ( )
- Stop-the-World    

# 사용 적합한 경우:
# - 배치 처리 애플리케이션
# - 응답 시간보다 처리량이 중요한 경우
Java
Parallel GC :
Multi-Thread GC

 GC Thread1:           
 GC Thread2:            ( GC)
 GC Thread3:           
 App Thread:             ()

CMS GC (Concurrent Mark Sweep)

Bash
# 사용 설정 (Java 9부터 deprecated, Java 14에서 제거됨)
-XX:+UseConcMarkSweepGC

:
- Old Generation GC   (Concurrent) 
- Stop-the-World  
- :    (Compact ), CPU  

 :
1. Initial Mark (STW): GC Root     ( )
2. Concurrent Mark:       (STW )
3. Remark (STW): 2     ()
4. Concurrent Sweep:     (STW )
Java
CMS GC :

 GC Thread:             
                                             
         Initial        Remark                 
           Mark         (STW)                  
           (STW)                               
 App Thread:                

G1 GC (Garbage-First GC)

Bash
# 사용 설정 (Java 9+ 기본값)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200  # 최대 STW 시간 목표 (기본 200ms)
-XX:G1HeapRegionSize=16m  # 리전 크기 (1~32MB, 2의 거듭제곱)

:
- Heap   Region 
- Young/Old   (Region  )
- Garbage  Region  (Garbage-First!)
-   STW  ( pause time  )
- Java 9+  GC
Java
G1 GC Heap :

 [E][E][E][O][O][ ][ ][ ][H][E][O][ ][E][E][E][O] 
 [O][ ][E][E][O][O][ ][H][E][ ][O][E][ ][E][O][ ] 
                                                     
 E = Eden Region                                     
 O = Old Region                                      
 H = Humongous Region ( )                     
 [ ] = Free Region                                   


 :
1. Young GC: Eden Region Survivor/Old  (, STW)
2. Concurrent Marking:  Heap    
3. Mixed GC: Old Region  Garbage   
4. Full GC:      ( )

ZGC (Z Garbage Collector)

Bash
# 사용 설정 (Java 15+ 정식 출시)
-XX:+UseZGC
-XX:SoftMaxHeapSize=8g  # 선호하는 최대 Heap 크기

:
-   GC     (Concurrent) 
- STW :  10ms  ()
- Heap     
-  TB Heap  
-  (Colored Pointer)   

# 사용 적합한 경우:
# - 초저지연(Ultra-low latency) 요구 사항
# - 대용량 Heap (수십 GB ~ 수 TB)
# - 실시간 처리 시스템
Java
ZGC  (STW ):

 GC Thread:         
                                                   
           Concurrent                   STW()    
           Mark/Relocate                             
 App Thread:      

Shenandoah GC

Bash
# 사용 설정 (Java 15+ 정식, OpenJDK)
-XX:+UseShenandoahGC

:
- ZGC   GC
- Concurrent Evacuation (    )
- Heap    pause  
- ZGC : Region   (ZGC Page )

GC 성능 지표와 트레이드오프

Java
GC  3  (   ):
1. Throughput (): GC      
2. Latency (): GC    
3. Footprint (): GC   

GC  :

             Parallel GC               
           G1 GC ()              
  (< 10ms)    ZGC, Shenandoah           
 /       Serial GC                  

GC 로그 분석

Bash
# GC 로그 설정 (Java 9+)
java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -Xlog:gc+heap=debug \
     -jar myapp.jar

# GC 로그 예시 (G1 GC)
[2024-01-01T10:00:01.234+0900][0.234s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 256M->128M(1024M) 12.345ms
# 의미: Young GC, 256MB → 128MB로 줄어듦, 전체 Heap 1024MB, 12.345ms 정지

[2024-01-01T10:00:10.567+0900][9.567s][info][gc] GC(5) Pause Full (G1 Evacuation Pause) 900M->200M(1024M) 245.678ms
# Full GC 발생 - 245ms 정지 → 튜닝 필요

# Java 8에서 GC 로그
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar myapp.jar

GC 튜닝 전략

Bash
# 1. Heap 크기 적절히 설정
-Xms4g -Xmx4g  # 초기=최대 (GC 오버헤드 감소, 재조정 비용 제거)

# 2. Young Generation 크기 조정 (Minor GC 빈도 조절)
-XX:NewRatio=2      # Young:Old = 1:2
-XX:NewSize=1g      # Young Gen 최소 크기
-XX:MaxNewSize=2g   # Young Gen 최대 크기

# 3. G1 GC Pause 목표 설정
-XX:MaxGCPauseMillis=100  # 100ms 이하 목표 (기본 200ms)

# 4. GC 스레드 수 설정
-XX:ParallelGCThreads=8      # STW GC 스레드 수
-XX:ConcGCThreads=4          # Concurrent GC 스레드 수 (CPU 코어의 1/4 권장)

# 5. 조기 승격(Premature Promotion) 방지
-XX:MaxTenuringThreshold=15  # Old Gen 승격 임계값 (기본 15)
-XX:SurvivorRatio=8          # Eden:Survivor = 8:1:1 (기본)

# 6. Large Object (Humongous Object) 처리
-XX:G1HeapRegionSize=16m  # Region 크기의 50% 이상 객체는 Humongous
# Humongous 객체는 Young GC에서 수거 안 됨 → 큰 객체 생성 최소화

메모리 누수 감지

Java
// 흔한 메모리 누수 패턴들

// 1. static 컬렉션에 계속 추가
class CacheManager {
  private static Map<String, Object> cache = new HashMap<>()

  // 제거 없이 계속 추가 → 메모리 누수
  public static void add(String key, Object value) {
    cache.put(key, value)  // ❌ 크기 제한 없음
  }

  // 해결: WeakHashMap 또는 크기 제한
  private static Map<String, Object> cache = new LinkedHashMap<>() {
    protected boolean removeEldestEntry(Map.Entry eldest) {
      return size() > 1000  // 1000개 초과 시 가장 오래된 항목 제거
    }
  }
}

// 2. 리스너/콜백 해제 안 함
class EventBus {
  private List<EventListener> listeners = new ArrayList<>()

  void register(EventListener listener) {
    listeners.add(listener)  // 등록은 하지만
  }

  // unregister가 없으면 listeners가 계속 쌓임
  void unregister(EventListener listener) {
    listeners.remove(listener)  // 반드시 해제 필요
  }
}

// 3. try-with-resources로 자원 해제
try (Connection conn = dataSource.getConnection()
     PreparedStatement ps = conn.prepareStatement(sql)) {
  // 자동으로 close() 호출 → 메모리/연결 해제
}

정리 표

GC 알고리즘Java 버전STW 시간처리량주요 용도특징
Serial GC모든 버전낮음단일 코어, 소규모단일 스레드
Parallel GCJava 8 기본중간높음배치 처리멀티 스레드
CMS GCDeprecated짧음중간저지연 (구)단편화 문제
G1 GCJava 9+ 기본예측 가능높음범용 (권장)Region 기반
ZGCJava 15+< 10ms높음초저지연대용량 Heap
ShenandoahJava 15+< 10ms높음초저지연OpenJDK

관련 질문

같은 카테고리/태그 기준