| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- java
- bootcamp
- Spring Boot
- aws
- 오블완
- Docker
- Redis
- 자격증
- spring
- 기본형
- nosql
- 개발자
- 자바
- mapping
- Cache
- QueryDSL
- MSA
- backenddeveloper
- jvm
- goorm x kakao
- serverless
- backend
- s3
- 티스토리챌린지
- goorm
- 스터디
- CodeCommit
- data
- jpa
- orm
- Today
- Total
gony-dev 님의 블로그
Garbage Collection 동작 원리 및 종류 본문
지난 시간에는 JVM의 동작 원리와 내부 구조에 대해 알아보았다.
이번에는 Excution Engine 내의 Garbage Collection의 동작원리와 종류에 대해 알아보는 시간을 가질 것이다.
출처 - Inpa Dev
Garbage Collection
줄여서 GC라고 하는 Garbage Collection는 힙 영역에 동적으로 할당된 메모리 중
더이상 필요하지 않은 메모리 객체를 회수하는 프로세스이다.
(Garbage Collector는 Garbage Collection이 동작을 수행하는 주체이다.)
이때 '더이상 필요하지 않은'이라는 의미는 스택 영역에서 더이상 참조하지 않는 값이나 데이터 필드를 의미한다.

이전 글에서도 언급했듯이 C언어는 더이상 사용하지 않는 객체가 있다면 개발자가 수동으로 메모리를 관리해야 하는 번거로움이 있었다.
하지만 Java언어는 GC가 메모리 관리를 대행해주기 때문에 개발자가 일일이 작업을 해야하는 사항이 줄어들고 이로 인해 더욱 개발에 집중할 수 있는 효율을 낼 수 있다.
하지만 언제나 단점도 존재하는 법, GC는 자동으로 동작하기에 메모리가 언제 해제되는지 정확히 알 수가 없으며, GC 실행동안에는 다른 작업이 정지되기 때문에 "Stop The World"라는 문제가 발생한다.
STW(Stop The World)는 GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는 것을 의미한다.
이때 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈추기에 보통 Garbage Collection Tuning을 통해 STW 시간을 줄이는 것이 관건이다.
Garbage Collection청소 방식
GC는 힙영역에서의 객체가 참조되고 있는지에 대한 여부를 확인하여 객체를 회수하는 방식이다.
이때 참조되지 않는 상태를 'unreachable'이라고 하는데 이 객체는 과연 어떤 방식으로 청소가 될까?
Mark and Sweep
"Mark and Sweep"은 GC에서 여러 객체를 분류하는 내부 알고리즘이다. 각 과정을 알아보자.

GC에는 GC Root라는 힙 외부에서 접근할 수 있는 변수나 오브젝트가 있다. 이 GC Root부터 시작하여 참조(연결)되는 오브젝트로 점점 뻗어나가게 되는데 이것이 바로 Mark이다.
이때 GC Root가 될 수 있는 것들은 실행 중인 쓰레드나, 정적/로컬 변수, JNI 레퍼런스가 있다.
Mark가 끝나면 GC는 힙 내부 전체를 순회하며 Mark되지 않은(=unreachable) 메모리들을 해제한다.
당연한 소리지만 Mark되지 않은 메모리는 곧 참조되지 않는 메모리이기에 필요가 없기 때문이다.
이 과정을 Sweep라 불린다.
Mark와 Sweep가 완료되었다면 하나의 과정이 더 진행되어야 완전히 청소 과정이 완료된다.
바로 Compact 과정으로, Sweep 이후에 분산된 객체들을 힙 영역의 시작 주소로 모아 메모리의 할당 여부에 따라 압축한다.
이해하기 조금 버겁하면 예를 들어 설명해 보겠다.
예를 들어 String person = new String("My name is Brian")이라는 객체 생성 코드가 있다고 하자.
new String("My name is Brian")은 힙 영역에 생성되고, person이라는 변수는 스택에 생긴다. GC 실행 시, GC Root인 person이 참조하는 메모리인 new String("My name is Brian")을 Mark할 것이다.
이후 메서드가 결과값을 반환하며 person이라는 변수가 사라지면 힙 영역의 new String("My name is Brian")은 아무도 참조하지 않으므로 Mark되지 않을 것이고, 결국 sweep 동작에 회수 될 수 있다.
이렇게 Mark and Sweep 방식을 사용하면 GC Root로부터 연결이 끊긴 순환 참조되는 객체들을 모두 지울 수 있다.
Garbage Collection 동작 과정

위의 그림을 보면 효율적인 메모리 관리를 위해, 객체의 생존 기간에 따라 물리적인 힙 영역을 나누었고,
Young과 Old Generation 두 가지의 영역이 나뉘게 되었다.
영역이 처음 설계될 때 아래의 2가지 사실을 전제 조건으로 삼았는데,
- 대부분의 객체는 금방 접근 불가능 상태가 된다.
- 오래된 객체에서 젋은 객체로의 참조는 아주 적게 존재한다.
Young Generation
- Young 영역은 새롭게 생성된 객체가 할당되는 영역이다.
- 대부분의 객체들은 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라지게 된다.
- 이처럼 Young 영역에 대한 GC를 Minor GC라고 불린다.
Old Generation
- Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 회수되는 객체는 적게 발생한다.
- Old 영역에 대한 GC를 Major GC나 Full GC라고 부른다.
Old 영역은 Young 영역보다 크게 할당받는데 그 이유는 Young 영역의 수명이 짧은 객체들은 금방 회수 되기에 큰 공간을 필요로 하지 않으며, 큰 객체들은 Young 영역이 아닌 Old 영역으로 바로 할당되기 때문이다.
깊은 이해를 위해 Young generation의 세부 구조를 알아보자.
1. Eden
- new를 통해 새로 생성된 객체가 위치하는 영역
- 정기적인 Garbage Collection 후에 살아남은 객체들은 Survivor 영역으로 보낸다.
2. Survivor 0 / Survivor 1
- 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
- Survivor 영역은 규칙이 존재하는데, 반드시 Survivor 0 이나 Survivor 1 중 하나는 비어 있어야 한다.
GC 동작 과정
1. Minor GC
- 처음 생성된 객체는 Young Generation 영역의 일부인 Eden에 위치
- 객체가 계속 생성되어 Eden 영역이 가득 차면 Minor GC가 실행
- Mark 동작을 통해 reachable 객체 탐색
- Eden에서 살아남은 객체는 1개의 Survivor 영역으로 이동
- Eden 영역에서 사용되지 않는 객체의 메모리를 해제
- 살아남은 객체는 Age 값이 1씩 증가
- 또다시 Eden 영역에 신규 객체들로 가득 차면 minor GC를 실행하고 mark 한다.
- marking한 객체들을 비어있는 Survivor 1으로 이동시키고 sweep한다.
- 다시 살아남은 객체들은 age 1 증가
- 1~9의 과정을 반복한다.
2. Major GC
- 객체의 age가 임계값에 도달하면 Old Generation으로 이동한다.
- 1의 과정이 반복되어 Old Generation 영역의 공간이 가득 차게 되면 Major GC가 발생한다.
Garbage Collection 종류
위의 GC의 과정을 살펴보면서 배운 점은 JVM이 자동으로 메모리를 관리해준다는 것이 엄청난 장점이라는 사실이다.
하지만 그와 동시에 Stop-The-World 문제를 개선해 나가는 방식도 필요하다는 사실 또한 모른 척할 수 없다.
자바가 발전함에 따라 힙 영역이 점점 커지며 지연 현상이 발생하는데,
이러한 현상을 완화하기 위해 도입한 여러가지 형태의 GC들을 알아보자.
1. Serial GC
서버 CPU 코어가 1개일 때 사용하기 위해 개발된 단순한 GC
GC를 처리하는 쓰레드가 1개이며, STW 시간이 가장 길다.
Minor GC에는 Mark-Sweep 청소 방식을 사용하며, Major GC에는 Mark-Sweep-Compact 청소 방식을 사용한다.
성능이 안 좋기 때문에 실무에서 사용하지는 않는다.
Serial GC를 사용하기 위해 다음 명령어를 입력하여 힙 메모리를 관리하도록 할 수 있다.(물론 잘 안쓰이지만..)
java -XX:+UseSerialGC -jar Application.java
2. Parallel GC
Java 8의 default GC.
Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 쓰레드로 개선하였다.
Serial GC에 비해 STW 시간이 감소되었다.
2번째 라인의 명령어로 사용할 쓰레드의 개수를 설정할 수 있다.
java -XX:+UseParallelGC -jar Application.java
# -XX:ParallelGCThreads=N
3. Parallel Old GC
Parallel GC를 개선한 버전으로 Young 영역 뿐만 아니라, Old 영역에서도 멀티 쓰레드로 GC를 수행한다.
새로운 GC 청소 방식인 Mark-Summary-Compact 방식을 사용한다.
java -XX:+UseParallelOldGC -jar Application.java
# -XX:ParallelGCThreads=N
4. CMS GC
어플리케이션의 쓰레드와 GC 쓰레드가 동시에 실행되어 STW 시간을 최대한 줄이기 위해 고안된 GC이다.
GC과정이 매우 복잡하고, GC 대상을 파악하는 과정이 복잡한 여러 단계로 수행되기 때문에 다른 GC와 비교하여 CPU 사용량이 높은 단점이 있다.
CMS GC는 결국 Java9 버전부터 deprecated 되었고, 이내 Java 14에서는 사용이 중지되었다.
java -XX:+UseConcMarkSweepGC -jar Application.java
5. G1 GC
CMS GC를 대체하기 위해 JDK 7 버전에서 최초로 release 된 GC
Java 9+ 버전의 default GC로 지정
4GB 이상의 힙 메모리, STW 시간이 0.5초 정도 필요한 상황에 사용한다.
기존 GC 알고리즘에서는 힙 영역을 물리적으로 고정된 Young / Old 영역으로 나누어 사용하였다. 하지만 G1 GC는 Region이라는 새로운 개념을 도입하여 사용한다. 전체 힙 영역을 Region이라는 영역으로 체스판 같이 분열하여 상황에 따라 Eden, Survivor, Old 등 역할을 고정이 아닌 동적으로 부여한다.
Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하기 때문에, GC 빈도가 줄어드는 효과를 볼 수 있다!
java -XX:+UseG1GC -jar Application.java
6. Shenandoah GC
Java 12에 release된 알고리즘
기존 CMS가 가진 단편화와 G1이 가진 pause의 문제를 해결한다!
강력한 동시성과 가벼운 GC 로직 덕분에 힙 영역의 크기에 영향을 받지 않고 일정한 pause 시간이 소요되는 것이 특징이다.
7. ZGC
Java 15에 release된 알고리즘
대량의 메모리를 낮은 지연률로 잘 처리하기 위해 디자인 된 GC이다.
G1의 Region처럼 ZGC는 ZPage를 사용하여, 2MB 배수로 동적으로 운영된다.
이때 ZGC의 큰 장점은 힙 크기가 아무리 증가하더라도 STW 시간이 절대 10ms를 넘지 않는 것이다.
'JVM' 카테고리의 다른 글
| Call by Value / Call by Reference (0) | 2025.12.14 |
|---|---|
| 자바 코드의 메모리 영역 (1) | 2025.12.10 |
| Garbage Collection Tuning (0) | 2025.12.08 |
| JVM 내부 구조 & 메모리 영역 (0) | 2025.12.01 |
| JDK / JRE / JVM 개념 (0) | 2025.11.24 |