[F-Lab 모각코 챌린지 4일차] Garbage Collector
- Garbage Collector란?
- GC의 수거대상
- GC의 동작순서
- Mark
- Sweep
- Compact
- GC가 일어나는 시점
- Minor GC
- Major GC
- stop-the-world
- GC 알고리즘
- Serial GC
- Parallel GC
- Parallel Old GC
- CMS GC
- G1 GC
Gabage Collector란?
JVM의 Heap 메모리에서 사용 중인 객체와 사용 되지 않는 객체를 식별하고 사용되지 않는 객체(garbage)를 삭제하는 프로세스
GC의 수거대상
레퍼런스가 없는 Unreachable한 객체
어떤 객체에 레퍼런스가 있다면 Reachable, 레퍼런스가 없다면 Unreachable이라고 한다. Unreachable한 객체들이 GC의 수거대상이 된다. 그렇다면 GC Roots가 될 수 있는 조건은 무엇일까? Heap 영역을 참조하는 메모리 영역들이다.
- stack 영역의 데이터들 (ex. 지역변수, 파라미터들)
- method 영역의 데이터들 (ex. static 변수)
- JNI(Java Native Interface)에 의해 생성된 객체들
GC의 동작순서
- Mark
- Sweep
- Compact
Mark
GC는 GC Root에서부터 모든 변수를 스캔하면서 어떤 객체를 참조하고 있는지 찾아서 마킹한다. 즉, reachable한 객체와 unreachable한 객체를 식별하는 과정이다.
Sweep
unreachable한 객체들을 Heap 영역에서 제거한다.
Compact
Sweep 후에 살아남은 객체들을 Heap 영역의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눈다. 이렇게 Compacting 작업을 해주면 메모리 단편화를 막을 수 있다. (GC 종류에 따라 하지 않는 경우도 있음)
🌀 메모리 단편화란 삭제된 객체들에 의해 메모리에 구멍이 난 것처럼 군데군데 비어있게 되는 현상을 말한다. 비어있는 메모리의 크기가 각각 1, 2, 2이고 내가 할당하려는 객체의 크기가 5이면 할당할 수 없다. 그런데 Compacting 작업을 통해 1, 2, 2로 단편화 된 메모리를 5로 합치면 해당 객체를 할당할 수 있다.
GC가 일어나는 시점
Minor GC
`Eden` 영역이 꽉 차면 Minor GC가 일어난다.
새로 생성되는 모든 객체는 `Eden` 영역에 할당된다. 이 그림에서 `Survivor` 영역도 비어있는 것을 확인할 수 있다.
만약 `Eden` 영역이 꽉 찬다? 그럼 Minor GC가 발생한다. 위에서 살펴봤던 Mark and Sweep 과정이 일어난다. 여기서 marking 된 살아남은 객체들은 `survivor` 영역으로 이동한다. `survivor0` 영역에 객체가 있으면 `survivor1` 영역은 비어있어야한다. 두 구역 중 어느 한 곳은 비어있는 상태여야한다. `survivor` 영역으로 이동한 reachable한 객체들은 age 값이 증가한다.
🌀 객체의 age란 무엇일까? `survivor` 영역에서 객체가 살아남은 횟수를 의미한다. 이 값은 Object Header에 기록된다. age가 임계치에 다다르면 `old` 영역으로 promotion 된다. JVM 중 가장 일반적인 HotSpot JVM의 경우 age의 기본 임계값은 31이다. 객체 헤더에 age를 기록하는 부분이 6 bit로 되어 있기 때문이다.
다시 `Eden` 영역에 새로운 객체들이 할당된다. 자리가 없으면 또 다시 Minor GC가 발생한다. 살아남은 객체들은 이번엔 `survivor1` 영역으로 이동한다. `survivor0` 영역과 `Eden` 영역의 unreachable한 객체들이 모두 제거된다.
Minor GC가 발생할 때마다 살아남은 객체들은 `survivor` 영역을 왔다갔다 한다. unreachable한 객체들은 다시 Sweep 되고 살아남은 객체들은 나이를 먹는다. 이런 과정들이 반복된다.
Major GC (=Full GC)
객체의 age가 age 임계치에 도달하면 `old` 영역으로 이동한다. 이를 객체가 promotion 됐다고 한다. `old` 영역이 꽉 차게 되면 Major GC가 발생한다. Major GC는 `old` 영역에 존재하는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제한다. `old` 영역은 `young` 영역보다 크고 `old` 영역에 할당되는 객체들도 크기가 크다. 그렇기 때문에 `old` 영역에서 객체를 제거하는데엔 많은 시간이 걸린다. 여기서 stop-the-world가 생기는 것이다. Major GC가 일어나면 스레드가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 애플리케이션이 멈추거나 버벅이는 현상이 일어난다. 이러한 이슈를 해결하고자 GC 알고리즘에 많은 발전이 있었다.
그런데 왜 Minor GC / Major GC를 나누어 실행할까? GC 설계자들은 몇 가지 가설을 통해 GC를 만들었다. 그 가설은 다음과 같다.
- 대부분의 객체는 금방 unreachable한 상태가 된다. 즉, 금방 garbage가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
Minor GC를 통해 꽤 많은 객체들이 수거된다. 이를 통해 메모리 낭비를 막을 수 있다. 그렇기 때문에 Minor GC는 꽤 빈번하게 일어나도 괜찮다. Major GC를 통해 수거되는 객체는 많지 않다. 이렇게 객체의 생존기간에 따라 물리적인 Heap 영역(young/old)을 나누고 각 영역에 따른 Minor GC와 Major GC를 수행하면 메모리를 효율적으로 관리할 수 있다.
stop-the-world
- GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것
- GC를 실행하는 쓰레드 외의 모든 쓰레드가 작업을 중단한다.
- GC의 종류에 따라 stop-the-world 발생 시간이 다르다.
GC 알고리즘
1. Serial GC
2. Parallel GC
3. Parallel Old GC
4. CMS GC (Concurrent Mark Sweep)
5. G1 GC (Garbage First)
Serial GC
- 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 GC
- GC를 처리하는 스레드가 1개(싱글 스레드)라서 stop-the-world 시간이 길다.
- Minor GC 에는 Mark-Sweep을 사용하고, Major GC에는 Mark-Sweep-Compact를 사용한다.
Parallel GC
- JAVA8의 default GC
- `young` 영역의 Minor GC를 멀티 스레드로 수행한다. (`old` 영역은 싱글 스레드로 수행) 그래서 Serial GC에 비해 stop-the-world 시간이 짧다.
Parallel Old GC
- Parallel GC를 개선한 GC
- `old` 영역에서도 멀티 스레드 방식의 GC를 수행한다.
- Mark-Summary-Compact 알고리즘을 사용한다.
- Sweep: 단일 스레드가 `old` 영역 전체를 훑는다.
- Summary: 멀티 스레드가 `old` 영역을 분리해서 훑는다.
CMS GC (Concurrent Mark Sweep)
- 애플리케이션의 스레드와 GC의 스레드가 동시에 실행되어 stop-the-world 시간을 줄이기 위해 고안됨
- reachable한 객체들을 한번에 찾지않고 순차적으로 찾는다.
- GC 대상을 파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다.
- compact 과정이 없어서 메모리 단편화 이슈가 아쉽다.
- CMS GC는 JAVA9 버전부터 deprecated 되었고 결국 JAVA14에서는 사용이 중지
- Initial Mark: GC Root에서 참조하는 객체들만 우선 식별
- Concurrent Mark: 이전 단계에서 식별한 객체들이 참조하는 모든 객체를 추적
- Remark: 이전 단계에서 식별한 객체를 다시 추적한다. 추가되거나 참조가 끊긴 객체 확정
- Concurrent Sweep: 최종적으로 unreachable한 객체들을 삭제
G1 GC (Garbage First)
- CMS GC의 메모리 단편화 이슈를 개선한 알고리즘
- JAVA9 이상의 default GC
- heap 영역을 `young` / `old` 영역 이렇게 물리적으로 나누지 않고 일정한 크기의 `region`으로 나눈다. 상황에 따라 `eden`, `survivor`, `old` 등 역할을 고정이 아닌 동적으로 부여
- 전체 heap이 아닌 `region` 단위로 탐색한다. 즉, 이전에 소개된 GC들처럼 일일이 메모리를 탐색하여 객체를 제거하지 않는다. 메모리가 많이 차있는 `region`을 인식하는 기능을 통해 해당 영역을 우선적으로 GC한다.
- 이전 GC 들은 `young` 영역에 Minor GC가 발생할 때마다 살아남은 객체들이 `eden` → `survivor0` → `survivor1`으로 순차적으로 이동했지만, G1 GC에서는 순차적으로 이동하지는 않는다. 대신 더 효율적이라고 생각하는 위치로 객체를 재할당 시킨다.
- garbage만 있는 `region`을 처음 수거해가기 때문에 Garbage First라는 이름이 붙은 것
- compact 진행
📚 참고
[10분 테코톡]🐥엘리의 GC
Java Garbage Collection Basics
☕ 가비지 컬렉션 동작 원리 & GC 종류 💯 총정리
'JAVA' 카테고리의 다른 글
[F-Lab 모각코 챌린지 6일차] equals(), hashCode() (0) | 2023.06.13 |
---|---|
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션 (0) | 2023.06.12 |
JVM 메모리 구조 (2) | 2023.06.10 |
[F-Lab 모각코 챌린지 2일차] 자바의 구동방식/Class Loader, Nested Class (0) | 2023.06.09 |
[F-Lab 모각코 챌린지 1일차] String (0) | 2023.06.08 |
댓글
이 글 공유하기
다른 글
-
[F-Lab 모각코 챌린지 6일차] equals(), hashCode()
[F-Lab 모각코 챌린지 6일차] equals(), hashCode()
2023.06.13 -
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션
[F-Lab 모각코 챌린지 5일차] Call by Value, Call by Reference in Java/어노테이션
2023.06.12 -
JVM 메모리 구조
JVM 메모리 구조
2023.06.10 -
[F-Lab 모각코 챌린지 2일차] 자바의 구동방식/Class Loader, Nested Class
[F-Lab 모각코 챌린지 2일차] 자바의 구동방식/Class Loader, Nested Class
2023.06.09