JVM의 Garbage Collector는 Heap 영역에 생성된 메모리가 Stack을 통해 도달하지 못하는 상황에 Heap 메모리를 정리해준다. 이러한 메모리는 Unreachable Memory라고 한다.
또한, Java의 gc 과정에 있어서 매우 중요한 개념이 있다. 그것은 바로 stop the world
이다.
: GC를 실행하기 위해 JVM이 application 실행을 멈추는 것. Stop the world가 발행하면 GC를 실행하는 쓰레드 외의 나머지 쓰레드는 모두 작업을 멈춘다. 어떤 GC를 사용하더라도 이 Stop the Word는 발생하는데, 대개의 경우 GC 튜닝이란 Stop the World의 시간을 줄이는 것이라고 한다.
GC에는 다양한 알고리즘이 존재하는데, 기본적인 Java Garbage Collection에 대해서 정리해보려고 한다. 또한, 가장 일반적인 JVM인 Hotspot JVM의 GC 과정에 대해서 알아보도록 하자.
기본적으로 C와 같은 언어는 개발자가 직접 메모리에 대해 free()
를 해야하지만, java는 스스로 쓰지 않는 메모리에 대해서 deallocating을 해준다. 아주 기본적인 automatic gc의 동작은 다음과 같다.
java에서는 프로그램 코드에서 명시적으로 가비지 콜렉팅을 하지 않는다. System.gc()
를 불러서 명시적으로 해제를 할 수 있지만, 이는 시스템 성능에 큰 영향을 끼치므로 절대 사용하면 안된다고 한다.
따라서 가비지 컬렉터는 자신이 스스로 알아서 가비지 콜렉팅을 한다. 가비지 컬랙터는 다음 두가지 가설을 기반으로 만들어졌다.
1. 대부분의 객체는 빠르게 unreachable하게 된다.
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
이러한 가설을 Weak Generational Hypothesis
라고 한다. 이러한 장점을 살리기 위해서 힙 공간을 크게 Young과 Old라는 두개의 공간으로 나눈다.
보통 Major GC가 일어나는 속도가 더 느린데, 그 이유는 모든 살아있는 객체들이 GC 판단의 대상에 전부 포함되기 때문이다.
따라서 반응성이 높은 application에 대해서는 이러한 major gc를 최소화하는 작업이 필요하다. 또한, major gc를 위해 일어나는 Stop the World
의 길이는 old generation space에 사용되는 garbage collector의 종류에 영향을 받는다.
Old 영역의 객체가 Young 영역의 객체를 참조하는 경우도 있기는 있다. 그 경우를 위해서 Old 영역에는 512바이트의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.
카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다.
Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별한다.
3개의 구성으로 이루어져 있다. - Eden 영역 - Survivor 영역 (2개)
그리고 객체가 생성되고 다음과 같은 과정을 거친다.
이 과정을 미루어 보았을 때, 항상 2개의 Survivor 영역 중 하나는 비어있게 된다.
Promotion은 GC를 하는 과정에서 계속 살아있는 객체가 다른 공간으로 이동할 때 aging이 되도록 한다.
HotSpot VM에서는 빠른 메모리 할당을 위해 bump-the-pointer
와 TLABs
라는 기술을 사용한다고 한다.
이는 추가로 이 글을 참고하면 좋을 것 같다.
NAVER D2
이 Old 영역에 대한 GC가 중요한데, 이에 대한 방식은 JDK 7 기준으로 5가지 방식이 있다.
운영서버에서는 절대 사용하면 안되는 방식. CPU 코어가 하나만 있을 때 사용하기 위해 만든 방식.
mark-sweep-compact
알고리즘을 사용하는데, 앞서서 설명한 기본적인 gc 방식때로 진행한다.
Serial과 기본 골자 알고리즘은 같다. 하지만 GC를 실행하는 Thread가 여러개이다. 따라서 빠르게 객체를 처리할 수 있다. 이 GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다. Throughput GC라고도 불린다.
Parallel GC와 Old 영역의 GC 방식만 다르다. mark-summary-compaction
과정을 거친다. Summary에서는 앞서 GC를 수행한 영역에 대해서 별도로 살아있는 객체를 식별한다는 점에서 Sweep과는 다르고, 좀 더 복잡한 단계를 거친다.
좀 더 복잡하다.
CMS는 객체를 마킹하는 과정에 Tri-color Marking Algorithm을 사용한다. 기존의 Mark and Sweep 과는 다른 차이점을 지닌다.
Trim-color Algorithm은 이걸 읽으면 더 이해가 잘간다.
(JVM) Garbage Collection Advanced | 오늘도 끄적끄적
이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.
그런데 CMS GC는 stop-the-world 시간이 짧다는 장점에 반해 다음과 같은 단점이 존재한다.
따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 한다.
그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.
G1 GC는 큰 메모리를 가진 멀티 프로세서 머신을 위해 만들어진 GC이다. 또한 CMS GC를 대체하기 위해 만들어진 GC이다.
CMS와 비교했을 때, G1 GC는 Compaction을 실시하는 GC이다. CMS보다 더 GC pause가 예측 가능하고, 사용자에게 pause target을 설정할 수 있도록 해준다.
G1 GC는 기존 GC와는 구조가 아예 다르다.
G1 GC는 바둑판 모양으로 Heap을 구조화하고, 각 영역에 객체를 할당하고 GC를 실행한다. 해당 영역이 꽉 차면 다른 영역에서 객체를 할당, GC를 실행한다.
G1 GC는 CMS와 비슷한 방식으로 작동한다.
처음에 concurrent global marking을 진행한다. 전체 객체의 이 작업을 통해 전체 heap에 대해서 객체가 참조되는지 아닌지에 대한 여부를 확인한다.
이 phase가 끝나면, G1은 어떤 부분이 거의 비었는지 (즉 사용되지 않는지) 파악할 수 있다. 이러한 region을 먼저 수집하는데, 거의 이런 부분이 많은 양의 빈 공간을 가져오게 된다.
이러한 작동방식 때문에 Garbage First
라는 이름을 가진다.
이 G1은 이렇게 다시 수집해올 수 있는 힙 공간에 대한 수집과 compaction 활동에 집중하고 있다. G1은 사용자가 원하는 pause time을 맞추고 지정한 정지 시간 타겟들을 기반으로 수집할 region의 개수를 고르기 위해 pause prediction model을 사용한다. (??)
이렇게 골라진 region에 대해서 evacuation이 진행된다. 1개 또는 다른 여러개의 region으로 부터 하나의 single region으로 객체들을 복사한다. 이 과정에서 free up과 compaction도 동시에 일어나게 된다. 이 Evacuation은 멀티프로세서로 concurrent하게 발생함으로서 (각 쓰레드가 맡고 있는 구역이 존재한다) 정지시간을 줄이고 throughput을 높인다.
더 중요한 점은, G1은 실시간 GC가 아니라는 것이다. 높은 가능성으로 Pause time을 맞출 수 있지만, 항상 맞출 수 있는 것은 아니다. 과거의 collection에 따라서, 유저가 정해진 시간에 맞추어 얼마나 많은 메모리를 정리할 수 있는지 G1이 계산을 한다.
이러한 방식 때문에 region을 모으는데 드는 비용에 대해 아주 정확한 모델을 가지고, G1은 얼마나 많은 region이 해당 pause time 안에 collection이 일어날 수 있는지 결정하는데 있어서 이 모델을 사용할 수 있다.
즉, 간단히 말하면 사용자가 결정한 pause 타임에 맞춰서 알아서 계산을 하면서 그것에 맞춰서 GC를 진행한다…? 라고 생각하면 될 것 같다.
자세한 G1 GC에 대한 과정, 그리고 CMS GC 와의 비교는 이를 참고하면 좋을 것 같다.
Getting Started with the G1 Garbage Collector