GC(Garbage Collector)
는 자바의 메모리를 자동으로 관리해주기 위해 JVM의 실행엔진(Execution Engine)에 위치한 소프트웨어입니다.
그런데 GC는 메모리를 관리하기 위해 CPU 자원을 사용하므로, GC가 수행될 때는 애플리케이션의 일시적인 정지(stop-the-world)
가 발생할 수 있습니다.
또한, stop-the-world 시간과 처리율(Throughput) 사이에는 상충관계가 발생할 수 있습니다. 즉, stop-the-world가 짧으면 GC 스레드가 차지하는 메모리가 커져 어플리케이션의 처리율이 좋지 않아지고, stop-the-world가 길면 GC 스레드가 차지하는 메모리가 작아서, 어플리케이션의 처리율이 좋아지는 것입니다.
여기서, 처리율은 Garbage Collection 에 소요되지 않은 총 시간의 백분율입니다.
공식문서 : "Throughput is the percentage of total time not spent in garbage collection considered over long periods of time"
공식문서에 따르면, G1(Garbage-First) GC
도 다른 GC와 마찬가지로 Heap 영역을 Young 영역과 Old 영역으로 나누어 관리합니다. 그렇게함으로써 메모리 확보를 Young 영역에 집중해서 효율적으로 구현할 수 있고, Old 영역에서 간헐적으로 메모리를 확보함으로써 stop-the-world 시간을 최소화시킵니다.
Young 영역과 Old 영역으로 나누는 이유는,
weak generational hypothesis
가정에 의한 것입니다. 자세한 내용은 해당 글을 참고해주세요.
G1 GC
는 일부 작업은 처리율을 향상시키기 위해 stop-the-world
를 발생시키고, 전체 Heap 영역을 대상으로 한 marking 작업과 같이 시간이 오래 걸리는 작업은 어플리케이션과 병렬로 처리합니다.
G1 GC
는 stop-the-world
를 짧게 유지하기 위해 G1은 메모리 확보(space reclamation)을 단계적으로 그리고 병렬로 점진적으로 수행합니다. 즉, 한 번에 모든 메모리를 회수하는 것이 아니라 점진적으로 회수한다는 의미입니다.
G1 GC
는 이전 GC 실행 결과와 Heap 상태를 기반으로 자동으로 IHOP(Initiating Heap Occupancy Percent)
를 수정합니다. G1 GC
는 IHOP
를 기준으로 GC가 동작하기 때문에, IHOP
를 어플리케이션의 상태에 맞게 최적으로 수정해나간다면 G1 GC
가 올바른 시점에서 Minor GC
와 Mixed GC
를 실행하고, 얼마나 많은 Heap 메모리를 할당해야 하는지 등을 최적으로 선택하게 됩니다.
G1 GC
는 Garbage-First Collector 라고도 불리는데, 이것은 garbage(unreachable objects)들로만 이루어진 region 부터 공간을 확보한다는 의미에서 붙여진 이름입니다.
즉, 위의 내용과 비슷한 맥락으로G1 GC
는 항상 효율적으로 GC를 실행한다는 의미를 가집니다.
G1(Garbage-First) GC
는 대용량 메모리가 있는 다중 프로세서 시스템을 대상으로 합니다. 구성할 필요가 거의 없이 높은 처리량을 달성하면서 높은 확률로 일시 중지 시간(stop-the-world)
목표를 충족하려고 시도합니다.
G1 GC
은 다음과 같은 기능을 갖춘 현재 대상 애플리케이션 및 환경을 사용하여 대기 시간과 처리량 간에 최상의 균형을 제공하는 것을 목표로 합니다.
공식문서에서는 G1 GC
는 다음과 같은 상황들에서 사용되는 것이 좋다고 얘기합니다.
live data
: 실제 어플리케이션에서 사용되기 위해 Root space로부터 참조되는 objectallocation
: Heap 영역에 객체가 할당되는 것promotion
: Heap 영역에서 GC가 동작해도 살아남아 유지되어 다른 메모리로 복사되는 것G1 GC는 어떻게 동작되고, 어떤 특징들을 가지는지 확인해보겠습니다.
위의 그림처럼 G1 GC는 힙 영역을 같은 크기의 region
으로 나누고 관리합니다. 나눠져서 관리되는 region
은 메모리 회수와 할당의 단위가 됩니다. 위의 그림에서 region 마다 다른 색깔을 띄고 있는데 각각은 다음과 같은 의미를 띕니다.
하나의 region 크기에도 저장되지 못할 정도로 큰 객체들(humongous objects)은 처음 객체가 저장될 때에도 Young 영역이 아닌, Old 영역으로 바로 저장됩니다.
공식 문서 : "...An application always allocates into a young generation, that is, eden regions, with the exception of humongous objects that are directly allocated as belonging to the old generation."
위의 그림은 G1 GC가 메모리를 회수할 때의 2가지 Garbage Collection 단계를 나타낸 그림입니다.
Young-only
Space Reclamation
간단히 말하면, Young-only
단계는 Old 영역으로 객체를 사용가능한 메모리에 할당시켜주는 단계이고, Space Reclamation
단계는 G1 GC가 Young 영역에 대해서 GC를 처리하는 것 이외에도 Old 영역에서의 GC를 동작시킴으로써 메모리를 회수하는 단계입니다.
Young-only
단계에서는 Young 영역에 저장된 객체들 중 Root space로부터 참조되는 객체들을 Old 영역으로 promotion 하게 됩니다. 또한, 메모리가 회수될 객체들은 marking 해두고, Space Reclamation
단계를 실행할지 여부를 결정짓게 됩니다.
Young-only
단계와 Space Reclamation
단계 사이의 전환은 Old Generation 점유가 특정 임계값인 Initiating Heap Occupancy Percent
임계값에 도달할 때 시작됩니다. 이 때 G1 GC가 동작하게 됩니다.
IHOP(Initiating Heap Occupancy Percent)
임계값이란, 힙 영역이 일정 수준 이상으로 채워졌을 때, 힙 영역에서 Mixed GC를 실행시키기 위한 threshold 값입니다. 디폴트 값은 45%
입니다.Mixed GC ?
G1 GC에 대한 설명에서
Mixed GC
라는 단어가 나왔습니다. 이는 Young 영역과 Old 영역을 구분짓지 않고 GC가 동작한다고 해서 붙은 이름입니다. 아래 설명을 보겠습니다.
Major GC
: Old 영역에서 수행되며, 전체 Heap을 스캔하여 더 이상 참조되지 않는 객체를 제거합니다. Major GC는 일반적으로 실행 시간이 길어질 수 있고, 어플리케이션의 일시 중지를 초래할 수 있으므로 최대한 자주 수행하지 않는 것이 좋습니다.Mixed GC
: Young 영역과 Old 영역을 동시에 수집하며, 일부 라이브 객체만을 수집하여 Young 영역으로 이동하거나 Old 영역에서 제거합니다. 따라서 일시 중지 시간이 짧고, 최적화된 방식으로 객체를 처리하여 성능을 향상시킬 수 있습니다.
즉, Mixed GC
는 일부를 대상으로 GC가 동작하는 것이고, Major GC(Full GC)
는 Heap 전체를 대상으로 GC가 동작하는 것입니다. 물론 G1 GC
에서도 다른 GC들과 같이 Major GC(Full GC)
가 동작하지만, Mixed GC
도 동작함으로써 stop-the-world 시간을 최소화시키고자 합니다.
앞에서 G1 GC는 Old 영역에서의 점유가 특정 임계값인 IHO(Initiating Heap Occupancy)
에 도달할 때 동작합니다. Concurrent Start young collection
은 일반 젊은 수집을 수행하는 것 외에도 마킹 프로세스를 시작합니다. Marking
은 다음의 Space Reclamation
단계에서 보관할 객체를 특정하기 위해 Old 영역에서 현재 reachable(라이브)한 모든 객체를 결정합니다.
단, Marking 이 완전히 완료되지 않은 상태에서도 Normal Young Collection
이 발생할 수 있습니다. Marking
은 다음의 두 가지 특별한 stop-the-world 발생한 뒤, 완료됩니다.
Remark
Cleanup
Remark
단계에서는 Marking 자체를 마무리하고, 전역 참조 처리 및 클래스 언로드를 수행하고 완전히 빈 영역을 회수하고 내부 데이터 구조를 정리(compact)합니다.
전역 참조 처리 = Root space로부터 unreachable 한 object GC
클래스 언로드 = 클래스로더가 Metaspace 영역으로 로딩해둔 클래스 관련 메타데이터를 GC
그리고 G1 GC는 Remark
단계와 Cleanup
단계 사이에서 나중에 Old 영역에서 여유 공간을 동시에 회수할 수 있도록, 정보(information)을 계산합니다. 이는 Cleanup
단계에서 완료됩니다.
정보(information)을 계산한다?
공식문서에서는 "G1 calculates information" 라고 나와있는데, 이는 G1 GC의 동작방식을 이해하면 간단하다.
G1 GC는 이전 GC의 동작과 Heap 영역의 상태를 고려해서 IHOP 값을 수정하며 stop-the-world와 처리율 간의 밸런스를 점진적으로 맞춰나갑니다. 이를Adaptive IHOP
라고 부릅니다.
그렇기에 G1 GC는 동작하면서 여러 정보들을 업데이트하고, 동적으로 IHOP 값을 수정해나갑니다.
이 단계에서는, 앞서 말한 것처럼 이전 GC의 동작과 Heap 영역의 상태를 고려해 정보(information)을 업데이트하고,이 단계 뒤에 Space Reclamation
단계가 실행될 것인지를 결정합니다. 지워야될 객체가 없다면 Space Reclamation
단계를 생략할 것이고, 만약 지워야될 객체가 있어서 Space Reclamation
단계가 동작한다면 Young-only
단계는 Single Prepare Mixed Young Collection
으로 전환됩니다.
G1 GC의 Space Reclamation
단계에서는 Young 영역과 Old 영역을 구분짓지 않고, 힙 영역에 저장되어 있는 객체들을 상대로도 메모리를 회수해가는 과정입니다.
다만, Space Reclamation
단계에서 Old 영역에 여유 공간이 충분하다면 지울 객체가 있더라도 더이상 메모리를 회수하지 않고, 종료됩니다.
그리고 앞에서 G1 GC
는 병렬로 어플리케이션과 같이 동작하며 객체들의 참조 여부를 체크한다고 했습니다. 그렇기에 G1 GC
는 어플리케이션이 동작하는 도중에 만약 여유 공간이 충분하지 않다면, Stop-the-world
를 발생시키고, Major GC(Full GC)
를 발생시켜 메모리를 압축시키고 여유 공간을 확보합니다.
G1 GC
는 Mixed GC
로 메모리를 최적화하다가, GC가 동작함에도 여유 공간이 부족하면, Major GC(Full GC)
가 동작함으로써 여유 공간을 확보한다.위에서는 G1 GC의 특징들에 대해 포괄적으로 알아봤다면 이제는 조금 디테일하게 알아보고자 한다.
G1 GC는 아래의 파라미터 값들을 고려해 Heap을 리사이징합니다.
-XX:InitialHeapSize
: 최소 Heap 사이즈-XX:MaxHeapSize
: 최대 Heap 사이즈-XX:MinHeapFreeRatio
: 최소 Heap 사이즈 비율-XX:MaxHeapFreeRatio
: 최대 Heap 사이즈 비율IHOP
란, Old 영역에서 할당되어 사용 중인 percentage를 의미합니다. 즉, Old 영역 중 40% 가 객체가 저장되어 있다면, IHOP
는 40이 되는 것입니다.
위에서도 언급했듯이, IHOP(Initiating Heap Occupancy Percent)
는 Young-only 단계의 Concurrent Start(객체 initial marking) 를 trigger해주는 임계값입니다.
그리고 이것 또한 언급했던 내용입니다. G1 GC
는 marking 하는 동안, marking에 소요되는 시간과 Old 영역에서 할당되는 메모리 양을 관찰하여 최적의 IHOP를 자동으로 결정합니다. 이를 Adaptive IHOP
라고 합니다.
Adaptive IHOP
기능은 -XX:G1UseAdaptiveIHOP
옵션으로 on/off가 가능합니다.
-XX:+G1UseAdaptiveIHOP
-XX:-G1UseAdaptiveIHOP
즉, Adaptive IHOP 기능을 사용하고 있다면, -XX:InitiatingHeapOccupancyPercent
값은 관찰 데이터가 부족할 때에만 유효한 IHOP 값이 되는 것입니다.
다만, Adaptvie IHOP 기능을 끈다면, -XX:InitiatingHeapOccupancyPercent
을 주면 계속 해당 IHOP 값을 유지하며, Old 영역의 점유율이 해당 값을 넘기게 되면 Concurrent Start 단계가 trigger 됩니다.
예를 들어, Old 영역에도 객체에게 할당할 메모리가 없어서 Full GC(Major GC)
가 발생했다고 가정해보겠습니다.
그렇다면, Adaptive IHOP
기능이 켜져있다면, IHOP
값은 내려가게 됩니다.
왜? IHOP 값이 내려가게 되면 조금 더 빨리 marking 작업이 이뤄지고 release 작업이 이뤄질 것입니다.
G1 GC
는 GC가 동작하는 과정에서 할당할 메모리 공간이 충분하다면, Full GC(Major GC)
가 발생하지 않고, Minor GC
와 Mixed GC
로 동작합니다.
Full GC
는 큰 Old 영역을 대상으로 GC가 동작하기에 오랜 시간이 소모되는 작업이라 어플리케이션의 성능에 큰 저하를 줍니다.Mixed GC
는 Young 영역과 Old 영역을 구분짓지 않고 GC가 동작하지만, 모든 영역을 스캔하는 것이 아니라 일부의 객체들만 수집해서 메모리를 회수해가기에 Full GC
에 비해 시간이 덜 소모됩니다.Minor GC
는 Young 영역에서 발생하는 GC라서 당연히 관리하는 메모리 크기가 작아 가장 빠른 시간 내에 완료되는 GC입니다.그렇기에 Adaptive IHOP
또한 그 점을 고려해서 Full GC
가 발생하지 않도록 계속 IHOP
값을 수정해나갑니다.
G1 GC의 Marking
은 SATB(Snapshot-At-The-Beginning)
알고리즘을 통해 구현되어 있습니다.
SATB
알고리즘은 marking cycle이 시작할 때, Heap 메모리에 있는 살아 있는 객체의 set을 logical snapshot
으로 저장합니다.
logical snapshot
은 marking cycle 시작 시점에 기존의 Heap 상태를 복사한 것으로, 이후에 변경되는 객체의 정보를 포함하지 않습니다.SATB
알고리즘은 새로 추가된 객체나 수정된 객체를 추적하기 위해 어플리케이션 스레드가 write 하는 모든 포인터(write barrier
)를 가로채어 처리합니다. 이때, SATB 알고리즘
은 remembered set
이라는 자료구조를 사용합니다.
remembered set
은 어플리케이션 스레드가 write 하는 포인터를 가지고 있는데, 이 포인터가 가리키는 객체가 heap에서 다른 영역으로 이동하거나, 아예 사라져도 그 포인터가 가리키는 객체를 살아 있는 객체로 추적할 수 있게 합니다.즉, SATB
알고리즘은 logical snapshot
의 일부인 객체들을 기록하거나 marking 하기 위해서 미리 작성한 barrier
를 사용합니다. remembered set
에는 객체가 가리키는 포인터 정보가 담겨져 있으며, 새로운 객체나 수정된 객체를 추적할 수 있게 합니다.
이를 통해 더 높은 성능을 발휘하며, 어플리케이션 스레드를 중지하지 않고도 marking 작업을 수행할 수 있습니다.
https://docs.oracle.com/en/java/javase/12/gctuning/garbage-first-garbage-collector.html
https://docs.oracle.com/en/java/javase/12/gctuning/garbage-first-garbage-collector-tuning.html
https://luavis.me/server/g1-gc
https://johngrib.github.io/wiki/java-g1gc
https://velog.io/@hanblueblue/GC-2.-G1GC-tuning
https://marknienaber.medium.com/jvm-tuning-with-g1-gc-76f27535f054