Garbage Collection
메모리 관리 방법 중 하나이다.
프로그래머가 동적을 할당한 메모리 영역 중 불필요한 영역을 찾아내고 해제하는 기능이다.
원래는 수동으로 이 작업을 시행했었다. 특히 옛날 언어(Fortran, C 등)에서 자주 보였다.
이로 인해 메모리 누수나 메모리 재사용 등의 버그가 자주 일어났었다.
그래서 이러한 문제를 해결하기 위해 해당 개념이 도입되었다.
JVM을 이용하는 언어인 Java나 Kotlin 등에서만 아니라 Ruby, Swift, Go 등의 여러 언어에서 사용하는 개념이다.
동작
일단 Java 에 대한 내용을 위주로 적을 것이기 때문에 JVM GC 내용을 적을 것이다.
알고리즘에 관계없이 매커니즘이 동일하다.
Young, Old
JVM 힙 메모리의 사용가능한 모든 Object를 트래킹해서 참조되지 않는 것들을 폐기하는 것이 기본이다.
JVM 힙 영역은 2가지의 전제로 설계되었다.
1) 대부분의 객체는 금방 접근 불가능한 상태가 된다. (Unreachable)
2) 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
즉, 객체는 대부분 일회성되며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다. 그렇기에 객체의 생존 기간에 따라 물리적인 힙 영역을 나누게 된다. Young, Old 2가지 영역이다.
- Young
- 새롭게 생성된 객체가 할당되는 영역
- 대부분의 객체가 Unreachable(참조되지 않은 상태) 되기 때문에 생성되었다가 사라진다.
- 3가지 영역으로 나뉘어진다.
- Eden
- new를 통해 새로 생성된 객체가 위치
- 정기적인 가비지 수집 후 살아남은 객체들은 Surivivor 영역으로 보낸다.
- Survivor 0 / 1
- 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
- Survivor 영역은 둘 중 하나는 비워야 하는 규칙이 있다.
- Old
- Young 영역에서 객체가 계속해서 참조되는 상태가 유지되면 해당 객체가 복사되는 영역
- Young 영역보다 크게 할당되고 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
이 때, Old 영역이 Young 영역보다 크게 할당되는 이유는 Young 영역의 수명이 짧은 객체들이 큰 공간을 필요로 하지 않기 때문이다.
Stop the world
이 동작은 JVM 동작 중 성능에 영향을 끼치는 동작이다. 이 작업은 JVM이 애플리케이션의 실행을 멈추는 작업이기 때문이다.
GC가 실행될 때는 GC를 실행하는 쓰레드를 제외한 모든 쓰레들의 작업이 중단되고 GC가 완료되면 작업이 재개된다. 이 시간을 최소화하는 것이 가장 큰 맹점이다.
Mark, Sweep
GC에서 사용되는 객체를 정리하는 기초적인 알고리즘이다.
Mark, Sweep 두 가지의 단계로 구분한다.
- Mark: 사용중인 메모리 조각과 그렇지 않은 메모리 조각을 식별한 후 마킹하는 단계
- Step: 마킹된 사용하지 않는 객체를 삭제하는 단계
종류
- Minor: Young 영역에서 발생되는 GC
- 처음 생성된 객체가 Young의 Eden에 위치
- Eden 영역이 차게 되면 Minor GC 발생
- Mark 동작으로 reachable 객체 탐색
- 살아남은 객체는 Survivor 영역으로 이동
- Unreachable 객체는 Sweep
- 이동된 객체는 age 값 증가
- Eden 영역에 신규 객체가 생성되고 다시 차게 되면 Minor GC 발생하고 Mark
- mark 된 객체들은 비어있는 Survivor 영역으로 넘어가게 되고 unreachable 객체는 Sweep
- 살아남은 객체는 age 값 증가
- 이러한 과정을 반복시킨다.
- Major: Old 영역에서 발생되는 GC, 실행 속도는 Minor GC 보다 느리다. 이유는 Old 영역이 Young 영역보다 크기가 크기 때문이다.
- Survivor 영역에서 객체의 age 값이 임계값에 도달하게 되면 Old 영역으로 이동된다. (Promotion)
- Promotion 된 객체가 Old 영역을 가득 채우게 되면 Major GC 가 발생된다. (Mark and Sweep)
알고리즘
-
Serial
- 가장 간단한 GC 구현체
- 싱글 스레드로 동작
- stop the world 시간이 가장 길다.
- 실무에서는 사용하는 경우가 없다.
-
Parallel
- Java 8의 default GC
- Serial GC와 기본적인 것은 같다.
- Young 영역의 Minor GC를 멀티 스레드로 수행
-
CMS
- 어플리케이션 스레드와 GC 스레드가 동시에 실행
- GC 과정이 복잡해진다.
- GC 대상을 파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다.
- 메모리 파편화 문제가 생긴다.
-
G1
- CMS를 대체하기 위하여 나온 GC로 jdk9+의 default GC
- 4GB 이상의 힙 메모리, stop the world 시간이 0.5초 정도 필요한 상황에 사용한다. 즉, 힙이 너무 작을 경우에는 사용하지 않음을 권장한다.
- 기존의 GC 알고리즘에서 사용하였던 Young/Old 영역이 아닌 Region 이라는 개념을 도입하여 사용. 분할하여 상황에 따라 Eden, Survivior, Old 등 역할을 동적으로 부여
- Garbage로 가득찬 영역을 빠르게 회수가 가능해 GC 빈도가 줄어드는 효과를 얻게 되는 원리
-
Shenandoah
- Java 12에 release
- CMS가 가진 단편화, G1이 가진 pause 이슈를 해결
- 강력한 Concurrency와 가벼운 GC 로직으로 힙 사이즈에 영향을 받지 않고 일정한 pause 시간이 걸린다.
-
Z
- Java 17의 default GC
- 대량의 메모리를 low-latency로 처리하기 위해 디자인
- ZPage라는 영역을 사용. Region과 다른 점이라면 Region은 크기가 고정이였던 반면, ZPage는 2mb 배수로 동적으로 운영
- 힙 크기가 증가하더라도 stop the world 시간이 절대 10ms를 넘지 않는다.
한계
- 메모리 누수가 발생할 수 있다. 더 이상 접근이 불가능한 객체만 회수하기 때문에 다시 사용하지 않는 객체라 할지라도, 객체로 접근할 수 있는 경로가 하나라도 남아있는 경우에는 GC는 객체를 사용할 가능성이 있다고 판단하고 회수하지 않는다.
참고