공부/자바

GC(Garbage Collectoin)에 대하여

xladmt 2025. 5. 22. 15:46

0. 들어가며

JVM을 공부하던 도중 실행 엔진에 속한 GC가 어떻게 동작하는지 궁금해졌다. JVM의 Heap 영역도 여러 영역으로 나뉘게 되어 생각보다 복잡하였다. 그래서 GC가 어떻게 Heap 메모리를 관리하는지 자세히 파헤쳐보려고 한다. 가비지 컬렉션(1,2)이 무엇인지, 어떻게 동작하는지(3, 4), GC 알고리즘(5), 마지막으로 GC 튜닝(6)을 간략하게 알아보고자 한다. Let's Go~!!

(GC에 대한 개념이 처음이라면 유튜브로 먼저 찾아보는 것을 추천합니다. 하단에 참고 링크 있음.)


1. GC(Garbage Collection)란?

가비지 컬렉션(Garbage Collection, GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체를 모아 주기적으로 제거하는 프로세스를 말한다.

C/C++ 언어에서는 이러한 가비지 컬렉션이 없어 프로그래머가 수동으로 메모리 할당과 해제를 일일이 해줘야 한다.

반면 Java에서는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 Java 프로세스가 한정된 메모리를 효율적으로 사용할 수 있게 하고, 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에 대해 관리하지 않아도 되어 개발에만 집중할 수 있다. Java 이외에도 파이썬, 자바스크립트, Go 언어 등 많은 프로그래밍 언어에서 가비지 컬렉션이 기본으로 내장되어 있다.

GC에도 몇 가지 단점이 존재한다. 자동으로 처리해준다 해도 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들다. 또한 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다. 이를 Stop-The-World라고 한다.

Stop-The-World(STW) 이란?
GC를 수행하기 위해 JVM이 프로그램 실행을 멈추는 현상을 의미한다.
GC가 작동하는 동안 GC 관련 Thread를 제외한 모든 Thread는 멈추게 되어 서비스 이용에 차질이 생길 수 있다. 따라서 이 시간을 최소화 시키는 것이 쟁점이다.

이로 인해, GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 한다. 따라서, 어플리케이션의 사용성을 유지하면서 효율적이게 GC를 실행하는 최적화 작업이 개발자의 숙제가 된다. 그리고 이러한 GC 최적화 작업을 GC 튜닝이라고 한다.


2. 가비지 컬렉션 대상

가비지 컬렉션(Garbage Collection)은 어떤 Object를 Garbage로 판단해서 스스로 지워버릴까?

가비지 컬렉션은 특정 객체가 garbage인지 아닌지 판단하기 위해서 도달성, 도달능력(Reachability) 라는 개념을 적용한다. 객체에 레퍼런스가 있다면 Reachable로 구분되고, 객체에 유효한 레퍼런스가 없다면 Unreachable로 구분하여 수거한다.

- Reachable : 객체가 참조되고 있는 상태
- Unreachable : 객체가 참조되고 있지 않은 상태 (GC의 대상이 됨)

JVM 메모리에서는 객체들은 실질적으로 Heap영역에서 생성되고 Method Area이나 Stack Area에서는 Heap Area에 생성된 객체의 주소만 참조하는 형식으로 구성된다.

하지만 이렇게 생성된 Heap Area의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 Heap Area 객체의 메모리 주소를 가지고 있는 참조 변수가 삭제되는 현상이 발생하면, 위의 그림에서의 빨간색 객체와 같이 Heap영역에서 참조되지 않은 객체(Unreachable)들이 발생하게 된다. 이러한 객체들을 주기적으로 가비지 컬렉터가 제거해주는 것이다.


3. 가비지 컬렉션 동작 방식

그럼 이제 GC가 Unreachable한 객체를 어떤 방식으로 청소를 하는지 알아보자!

 

3-1. Mark And Sweep

Mark&Sweep이란 가비지 컬렉션이 될 대상 객체를 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행하게 된다.

Mark and Sweep

Mark 과정 : 먼저 Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.

Sweep 과정 : 참조하고 있지 않은 객체 즉, Unreachable 객체들을 Heap에서 제거한다.

Compact 과정 : Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다. (가비지 컬렉터 종류에 따라 하지 않는 경우도 있음)

이렇게 Mark And Sweep 방식을 사용하면 루트로부터 연결이 끊긴 순환 참조되는 객체들을 모두 지울 수 있다.

GC의 Root Space 란?
Mark And Sweep 방식은 루트로 부터 해당 객체에 접근이 가능한지가 해제의 기준이 된다.
JVM GC에서의 Root Space는 Heap 메모리 영역을 참조하는 method area, static 변수, stack, native method stack이 되게 된다.
Root Space

 


4. 가비지 컬렉션 동작 과정

이제 가비지 컬렉션 동작 과정을 알아보자! 그 전에 JVM의 Heap 메모리 구조를 알아야하기 때문에 Heap 영역부터 자세히 알아보겠다.

 

4-1. heap 메모리의 구조

JVM의 힙(heap) 영역은 동적으로 레퍼런스 타입의 데이터가 저장되는 공간으로서, 가비지 컬렉션의 대상이 되는 공간이다.

Heap 영역은 처음 설계될 때 다음의 2가지를 전제(Weak Generational Hypothesis)로 설계되었다.

‣ 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
‣ 오래된 객체에서 새로운 객체로의 참조는 아주 적제 존재한다.

즉, 객체는 대부분 일회성이 되며, 메모르에 오랫동안 남아있는 경우는 드물다는 것이다. 이러한 특성을 이용해 JVM 개발자들은 보다 효율적인 메모리 관리를 위해, 객체의 생존 기간에 따라 물리적인 Heap 영역을 나누게 되었고 크게 YongOld 총 2가지 영역으로 설계하였다. 그리고 더욱 효율적인 GC를 위해 Young 영역을 3가지 영역(Eden, survivor0, survivor1)으로 나눴다.

 

각각 무엇이고 어떤 특징이 있는지만 알고 넘어가자! (GC 동작 과정으로 이해하면 더 쉽기 때문)

Young 영역 : 새로운 객체들이 할당되는 영역
      - Eden : new를 통해 새로 생성된 객체가 위치. 정기적인 쓰레기 수집 후 살아남은 객체들은 Survivor 영역으로 보냄
      - Survivor 0 / Survivor 1 : 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역. Survivor 0 또는 1 둘 중 하나는 꼭 비어있어야 함. 
- Old 영역 : Young 영역에서 오랫동안 살아남은 객체들이 존재하는 영역

 

4-2. Minor GC의 동작 과정

Young Generation 영역은 짧게 살아남는 메모리들이 존재하는 공간이다. 모든 객체는 처음에는 Young Generation에 생성되게 된다. Young Generation의 공간은 Old Generation에 비해 상대적으로 작기 때문에 메모리 상의 객체를 찾아 제거하는데 적은 시간이 걸린다.

이 때문에 Young Generation 영역에서 발생되는 GC를 Minor GC라 불린다. 동작 순서를 제대로 알아보자.

 

  1. 처음 생성된 객체는 Young Generation 영역의 일부인 Eden 영역에 위치
  2. 객체가 계속 생성되어 Eden 영역이 꽉차게 되고 Minor GC가 실행
  3. Mark 동작을 통해 reachable 객체를 탐색
  4. Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동
  5. Eden 영역에서 사용되지 않는 객체(unreachable)의 메모리를 해제(sweep)
  6. 살아남은 모든 객체들은 age 값이 1씩 증가
  7. 또다시 Eden 영역에 신규 객체들로 가늑 차게 되면 다시 한 번 Minor GC가 발생하고 mark한다.
  8. marking 한 객체들을 비어있는 Survivor 1으로 이동하고 sweep
  9. 다시 살아남은 모든 객체들은 age가 1씩 증가
  10. 위 과정들을 반복한다.
age 값이란?
Survivor 영역에서 객체의 객체가 살아남은 횟수를 의미하는 값이며, Object Header에 기록된다.
만일 age 값이 임계값에 다다르면 Promotion(Old영역으로 이동)여부를 결정한다. JVM 중 가장 일반적인 HotSpot JVM의 경우 이 age의 기본 임계값은 31이다. 객체 헤더에 age를 기록하는 부분이 6bit로 되어 있기 때문이다.

또한 Survivor 영역의 제한 조건으로 Survivor 영역 중 반드시 1개는 사용되어야 하고, 나머지는 비어 있어ㅑ 한다. 만약, 두 Survivor 영역에 모두 데이터가 존재하거나, 모두 사용량이 0이라면 현재 시스템이 정상적인 상황이 아니라는 반증이 된다.

 

4-3. Major GC의 동작 과정

Old Generation은 길게 살아남은 메모리들이 존재하는 공간이다. Old Generation의 객체들은 거슬러 올라가면 처음에는 Young Generation에 의해 시작되었으나, GC 과정 중에 제거되지 않은 경우 age 임계값이 차게 되어 이동된 녀석들이다.

그리고 Major GC는 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생하게 된다. (Major GC는 Full GC라고도 불린다.)

 

  1. 객체의 age가 임계값에 도달하게 되면, 이 객체들은 Old Generation으로 이동한다. 이를 promotion이라 부른다.
  2. 위의 과정이 반복되어 Old Generation 영역의 공간(메모리)가 부족하게 되면 Major GC가 발생한다.
Major GC란?
  Major GC는 Old 영역은 데이터가 가득 차면 GC를 실행하는 단순한 방식이다. Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 Major GC가 실행되게 된다. 하지만 Old Generation은 Young Generation에 비해 상대저긍로 큰 공간을 가지고 있어, 이 공간에서 메모리 상의 객체 제거에 많은 시간이 걸리게 된다.

  예를 들어, Young 영역은 일반적으로 Old 영역보다 크기가 작기 때문에 GC가 보통 0.5초에서 1초 사이에 끝난다 그렇기 때문에 Minor GC는 애플리케이션에 크게 영향을 주기 않는다. 하지만 Old 영역의 Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용한다.

  바로 여기서 Stop-The-World 문제가 발생하게 된다. Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 현상이 일어나기 때문이다. 따라서 자바 개발자들은 끊임 없이 가비지 컬렉션 알고리즘을 발전 시켜왔다.

 

4.4 Minor GC와 Major GC의 차이점


5. GC 알고리즘의 종류

GC를 수행하기 위해 Stop-The-World가 발생되고 이 때문에 애플리케이션이 중지되는 문제점이 발생하게 된다. 또한, 자바가 발전됨에 따라 Heap의 사이즈가 커지면서 애플리케이셔의 지연(Suspend)현상이 두드러지게 되었고, 이를 최적화 하기 위해 다양한 Garbage Collection(가비지 컬렉션) 알고리즘이 개발되었다.

여기서 알아두어야 할 것은 소개할 GC 알고리즘은 모두 설정을 통해 Java에 적용할 수 있다는 점이다. 즉, 상황에 따라 필요한 GC 방식을 설정해서 사용할 수 있다. 

 

5-1. Serial GC

  • 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 GC
  • GC를 처리하는 스레드가 1개(싱글 스레드) 이어서 가장 Stop-The-Wolrd 시간이 길다.
  • Minor GC에는 Mark-Sweep을 사용하고, Major GC에는 Mark-Sweep-Compact를 사용한다.
  • 보통 실무에서 사용하는 경우는 없다.(디바이스 성능이 안좋아서 CPU 코어가 1개인 경우에만 사용)

더보기

Serial GC 실행 명령어

자바 프로그램을 실행할 때 -XX:+UseSerialGC GC 옵션을 지정하여 해당 가비지 컬렉션 알고리즘으로 힙 메모리를 관리하도록 실행할 수 있다.

java -XX:+UseSerialGC -jar Application.java

 

5-2. Parallel GC

  • Java 8의 디폴트 GC
  • Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 스레드로 수행(Old 영역은 여전히 싱글 스레드)
  • Serial GC에 비해 Stop-The-World 시간 감소

더보기

Parallel GC 실행 명령어

  • GC 스레드는 기본적으로 CPU 개수만큼 할당된다.
  • 옵션을 통해 GC를 수행할 스레드의 갯수 등을 설정해줄 수 있다.
java -XX:+UseParallelGC -jar Application.java
# -XX:ParallelGCThreads=N : 사용할 스레드의 갯수

 

5-3. Parallel Old GC (Parallel Compacting Collector)

  • Parallel GC를 개선한 버전
  • Young 영역 뿐만 아니라, Old 영역에서도 멀티 스레드로 GC 수행
  • 새로운 가비지 컬렉션 청소 방식은 Mark-Summary-Compact 방식을 이용(Old 영역도 멀티 스레드로 처리)

더보기

 Parallel Old GC 실행 명령어

java -XX:+UseParallelOldGC -jar Application.java
# -XX:ParallelGCThreads=N : 사용할 스레드의 갯수

 

5-4. CMS GC (Concurrent Mark Sweep)

  • 어플리케이션의 스레드와 GC 스레드가 동시에 실행되어 Stop-The-World 시간을 최대한 줄이기 위해 고안된 GC
  • 단, GC 과정이 매우 복잡해짐
  • GC 대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다
  • 메모리 파편화 문제
  • CMS GC는 Java9 버전부터 deprecated 되었고, 결국 Java 14에서는 사용이 중지

더보기

 CMS GC 실행 명령어

# deprecated in java9 and finally dropped in java14
java -XX:+UserConcMarkSweepGC -jar Application.java

 

5-5. G1 GC(Garbage First)

  • CMS GC를 대체하기 위해 jdk 7버전에서 최초로 release된 GC
  • java 9+ 버전의 디폴트 GC로 지정
  • 4GB 이상의 힙 메모리, Stop-The-World 시간이 0.5초 정도 필요한 상황에 사용 (Heap이 너무 작을 경우 미사용 권장)
  • 기존의 GC 알고리즘에서는 Heap 영역을 물리적으로 고정된 Young/Old 영역으로 나누어 사용하였지만, G1 GC는 아예 이러한 개념을 뒤엎는 Region이라는 개념을 새로 도입하여 사용. 전체 Heap 영역을 Region이라는 영역으로 체스같이 분할하여 상황에 따라 Eden, Survivor, Old 등 역할을 고정이 아닌 동적으로 부여
  • Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로, 결국 GC 빈도가 줄어드는 효과를 얻게 되는 원리

G1 GC의 효율성
  Java9+ 부터 기본 GC로 자리잡은 G1 GC에서는 이전의 GC들처럼 일일히 메모리를 탐색해 객체들을 제거하지 않는다. 대신 메모리가 많이 차있는 영역(region)을 인식하는 기능을 통해 메모리가 많이 차있는 영역을 우선적으로 GC 한다. 즉, G1 GC는 Heap Memory 전체를 탐색하는 것이 아닌 영역(region)을 나눠 탐색하고 영역(region)별로 GC가 일어난다.
 
또한 이전의 GC 들은 Young Generation에 있는 객체들이 GC가 돌때마다 살아남으면 Eden → Survivor0 → Survivor1으로 순차적으로 이동했지만, G1 GC에서는 순차적으로 이동하지는 않는다. 대신 G1 GC는 더욱 효율적이라고 생각하는 위치로 객체를 Reallocate(재할당) 시킨다. 예를 들어 Survivor1 영역에 있는 객체가 Eden 영역으로 할당하는 것이 더 효율적이라고 판단될 경우 Eden 영역으로 이동시킨다.
더보기

 G1 GC 실행 명령어

java -XX:+UserG1GC -jar Application.java

 

 

5-6. Shenandoah GC

  • Java 12에 release
  • 레드 햇에서 개발한 GC
  • 기존 CMS가 가진 단편화, G1이 가진 pause의 이슈를 해결
  • 강력한 Concurrency와 가벼운 GC 로직으로 heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요되는 것이 특징

더보기

 Shenandoah GC 실행 명령어

java -XX:+UseShenandoahGC -jar Application.java

 

5-7. ZGC (Z Garbage Collector)

  • Java 15에 release
  • 대량의 메모리(8MB ~ 16TB)를 low-latency로 잘 처리하기 위해 디자인 된 GC
  • G1의 Region 처럼, ZGC는 ZPage라는 영역을 사용하며, G1의 Region은 크기가 고정인데 비해, ZPage는 2mb 배수로 동적으로 운영됨. (큰 객체가 들어오면 2^ 로 영역을 구성해서 처리)
  • ZGC가 내세우는 최대 장점 중 하나는 힙 크기가 증가하더라도 'Stop-The-World'의 시간이 절대 10ms를 넘지 않는다는 것

더보기

 ZGC 실행 명령어

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar Application.java

 

 

 

[참고]

https://www.youtube.com/watch?v=vZRmCbl871I

 

https://www.youtube.com/watch?v=FMUpVA0Vvjw

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC

 

☕ 가비지 컬렉션 동작 원리 & GC 종류 💯 총정리

Garbage Collection(GC) 이란? 가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객

inpa.tistory.com

https://velog.io/@yarogono/Java%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%ED%84%B0Garbage-Collector%EB%9E%80

 

[Java]가비지 컬렉터(Garbage Collector)란?

JVM GC가 Heap에서 사용하지 않는 객체를 메모리를 해제해준다. 하지만 어떻게 동작하고 어떤 알고리즘을 사용 하는지 알아보지 못해서 글로 작성해봤다. 단순히 암기가 아닌 시각적인 사진들을

velog.io

https://velog.io/@impala/JAVA-JVM-Garbage-Collection

 

[JAVA] JVM - Garbage Collection

JVM의 메모리공간을 효율적으로 관리해주는 Garbage Collector가 하는 일에 대해 알아본다

velog.io