
Computer Science에서 Garbage Collecting이란 자동 메모리 관리 시스템을 의미합니다.
사용하는 객체는 모아서 재사용할 수 있도록 정리하고, 더 이상 사용하지 않는 객체(Unreachable Object)가 저장되어있던 공간은 다른 데이터로 덮어쓰이게 됩니다.
C 같은 언어는 개발자가 직접 메모리를 관리해주어야 하는 반면, Java와 같이 Garbage Collector가 있는 언어는 직접적으로 메모리를 관리할 필요가 없습니다.
이번에는 Java 9부터 Default GC로 자리 잡은 G1 GC에 대해 알아보겠습니다.
G1 GC(Garbage First GC)는 객체의 가비지 비율이 높은 리전(Region)부터 회수하는 GC입니다. 기존보다 큰 메모리와 멀티프로세서 환경에서, 예측 가능하고 최대한 짧은 Pause time을 구현하기 위해 고안된 GC입니다.
G1 GC는 힙 영역의 메모리 구조가 기존과는 다릅니다. 예컨대, 4GB 환경에서 2MB짜리 리전이 2000여개 있는 환경을 생각해볼 수 있습니다. 전통적인 GC의 영역 구분과 같이 Eden, Survivor, Old 영역으로 구획되어 있지만 리전은 그 때의 상황에 맞추어 동적으로 지정될 수 있습니다.

G1 GC는 Young GC, Mixed GC, Full GC를 통해 리전을 회수합니다. 여기서 Full GC는 Young GC와 Mixed GC가 원활하게 이루어지지 못했을 때 발생합니다.
이외에는 Young GC와 Mixed GC가 순환하며 GC가 이루어집니다.

새로운 객체가 저장되는 Eden 영역이 가득 차면 발생하는 GC입니다. Eden 영역에 있는 객체가 생존하여 Survivor 영역으로, Survivor 영역에 있던 객체는 Old 영역으로 이동합니다. 안전한 객체 대피를 위해 STW(Stop The World)가 먼저 발생하고 객체가 이동합니다.
객체를 다른 리전으로 옮기고 정리하기 전에(evacuate and compact) 어플리케이션을 잠시 멈추는 것을 의미합니다.
참조의 무결성을 위해 이루어집니다. 어플리케이션이 사용되고 있는 와중에 객체가 이동하게 되면 다른 위치가 참조되거나, 객체 이동 중 참조 변수의 값이 바뀐다면 무결성이 깨질 위험이 있기 때문입니다.
Old 영역도 객체로 가득 차면 Concurrent Cycle이 시작됩니다. 즉, Old 영역의 비중인 IHOP(Initiating Heap Occupancy Percent, 기본 45%)가 임계 수준에 다다르면 Young Collection을 할 때 Concurrent Mark Cycle의 시작인 Initial Mark도 트리거됩니다. 이 임계 수준은 목표 PauseTime에 따라 런타임 중 Adaptive하게 변경됩니다.
GC Root 영역에서 출발하여 참조 그래프를 따라 재귀적으로 도달 가능한 객체들을 마킹하는 단계입니다. Young Collection의 STW 때 이루어지며, STW 종료 후에도 GC 시작 단계인 Initial Mark 단계에서의 객체 참조 상태를 기억하기 위해 SATB(Snapshot-At-The-Beginning) 알고리듬을 이용하여 스냅샷(Snapshot)을 설정합니다.
- GC Root란?
클래스 static 변수, JVM 스택에서 사용 중인 객체, JNI 레퍼런스 등 사용 중인 객체 추적의 시작점이 되는 지점입니다.

힙 영역 전체를 탐색하며 객체를 마킹합니다. STW이 종료되고 나서 앱은 재구동되어도 GC 스레드는 동시에 마킹을 진행합니다. Write Barrier를 사용하여 실시간으로 객체 참조가 변경되어도 변경사항을 RSet과 Card Table을 수정합니다.
- RSet(Remember Set)
각 리전별로 외부에서 해당 리전을 참조하는 객체 정보가 담긴 자료구조입니다.
Concurrent Mark가 마무리되는 시점입니다. STW 상태에서 SnapShot을 한 번 더 생성하여 Intial Mark 때 생성했던 Snapshot과 비교하여 Concurrent Mark에서 미처 마킹하지 못한 객체들을 마킹합니다. 또한, 완전히 비어 있는 리전은 회수하여 다시 재할당 가능한 리전 목록(free list)에 추가합니다.
이후 Clean up 단계 전에 수집할 가치가 있는, 즉 Garbage 비율이 높은 리전들을 우선적으로 선정합니다. 이는 C Set(Collection Set)이라는 회수 대상 리전들로 지정되어 Clean up 단계에서 실제로 회수됩니다.
이 단계에서는 최종적으로 공간 회수 여부가 결정되고, 회수가 결정되면 Mixed GC를 준비합니다. 만약 공간 회수가 필요 없는 것으로 결정되면 Young GC로 사이클로 돌아가게 됩니다.
Mixed GC가 이루어지는 단계입니다. Young 리전과 Old 리전 함께 수집될 수 있습니다.
CSet을 사용하여 회수가 이루어지고 마킹된 객체는 다른 영역으로 대피(evacuate)하게 됩니다.
Mixed GC는 수차례 실행될 수 있고 Garbage 비율이 충분한 리전이 남아있지 않거나, 전체 힙 영역에서 Garbage 비율이 충분히 낮아졌을 때, Mixed GC counter 임계치에 다다르면 더 이상 진행하지 않습니다.
G1 GC에서는 full gc가 발생하면 optimal하게 돌아가고 있지 않다는 걸 의미합니다. Full GC의 트리거는 evacuation failuare와 humongous 객체 삭제 제한 등입니다. 싱글 스레드로 작동하며 개발자들은 Full GC가 이루어지기 전에 조치를 취해야 하겠습니다.
Region 크기의 절반이 넘는 객체를 의미합니다. 높은 리소스를 사용하는 객체의 이동을 막기 위해 생성될 때부터 Old 영역에 생성됩니다. humongous 객체가 속한 리전은 Concurrent Cycle에서도 외부 참조가 없다면 수집될 가능성이 있습니다. (JDK 8u60 버전부터)
String s = new String("문자열");
String을 객체 형태로 생성하면 기존에는 그 갯수만큼 생성이 되었습니다. 그러나 메모리 낭비를 막기 위해 똑같은 String 객체가 생성되었을 경우에는 하나만 생성하여 여러 변수가 한 객체를 가리키게 최적화하였습니다.
JDK 9 버전 이전에는 힙 점유율 변수 InitiatingHeapOccupancyPercent(IHOP)가 concurrent mark의 Mixed GC 트리거였습니다.
JDK 9 버전 이후에도 최초에는 IHOP가 트리거이지만 런타임 중에 GC가 목표 PauseTime에 맞추어 Adaptive하게 트리거를 변경시킵니다.
G1 GC에서 주로 사용되는 변수들입니다. 동작 원리와 튜닝 등을 위해 알아야하는 변수들을 모아보았습니다.
GC 중 발생하는 애플리케이션의 최대 멈춤 시간입니다. (Default 200ms)
설정된 최대 멈춤 시간 이내로 끝내기 위해 메모리 영역을 얼마나 청소할지 결정합니다. GC의 성능을 향상시킬 수 있지만, 멈춤 시간을 줄이기 위해 처리량이 희생될 수 있습니다.
Mixed GC를 시작하는 트리거인 힙 점유율 임계치입니다. (Default 45%)
리전 1개의 메모리 크기입니다. (Default: 1MB ~ 32MB)
영역 크기가 작을수록 GC 관리의 세밀도가 증가하지만 오버헤드가 증가할 수 있고, 임의로 조정했을 때 GC가 optimal하게 작동하지 않을 수 있습니다.
G1 GC의 멀티스레드 작업 시 사용할 쓰레드 수입니다. 기본적으로 시스템의 CPU 코어 수에 따라 자동으로 설정됩니다.
Concurrent Marking 단계에 병렬적으로 사용할 스레드 수입니다.
힙의 여유 메모리입니다. Full GC를 방지하기 위해 조정할 수 있습니다. (Default 10%)
Eden 영역과 Survivor 영역 간의 비율로 Regular Young GC의 트리거입니다. (Default 8)
즉, Survivor 영역에 비해 Eden 영역이 너무 많아지면 Young GC가 이루어집니다.
Mixed GC에서 Old 리전을 수집할지 결정하는 기준입니다. (Default 85)
예컨대, Old 영역의 리전에서 살아있는 객체의 비율이 85% 이하일 경우 Mixed GC 대상인 Cset에 추가됩니다.
힙 전체 영역의 가비지 비율을 조절하는 변수입니다. (Default 10)
가비지 비율이 10% 넘어가면 추가 Mixed GC를 합니다.
최대 Mixed GC 횟수 목표치입니다. (Default 8)
오히려 Default값이 가장 효율적인 경우가 가장 많아 GC 전문가들은 우선 코드 레벨에서 최적화하길 권장한다고 합니다.
출처
Java Garbage Collection Basic
Getting Started with the G1 Garbage Collector
G1 GC of JDK 9