앞에서는 JVM의 메모리 구조에 대해서 배웠었다.
이번엔 JVM의 GC과정에 대해 배우고 그 메모리 구조를 어떻게 Garbage Collection에서 활용하는지 배워보자
이미지 출처 : https://nays111.tistory.com/110
Major GC는 Old Gen 영역을 정리하기 위한 GC이다.
JDK 버전과 GC 알고리즘에 따라서 정확히 언제 수행하는 지는 달라진다.
Minor GC 와 Major GC 가 동시에 이루어질 때. YoungGen, Old gen 모두 가용 영역이 부족할 때 생긴다.
Full GC 때 STW(stop-the-world)가 일어난다.
CMS(concurrent mark and swap), G1 GC의 경우 이 Full GC를 최소화 하도록 설계되었다.
다만, 알고리즘이 모든 것을 보장해주지 않는다.
프로그래밍 된 코드가 객체의 주기를 짧게 가져가고 오래 살아남는 객체를 최소화 한다면, Full GC가 최소한으로 일어나도록 할 수 있다. 코드가 더욱 중요하다.
출처 : https://imasoftwareengineer.tistory.com/103
GC Root 될 수 있는 종류 세 가지.
JVM stack 의 변수
스택에 있는 변수인데 스택에서 가장 먼저 시작하는 클래스나 변수
Method Area 의 static 데이터
Static 변수나 클래스 정보는 Method Area에 저장된다. 이 영역은 클래스 로딩 시부터 종료 시까지 JVM에 의해 유지되기 때문에, static 데이터 역시 GC Root가 될 수 있다. 이 static 데이터가 다른 객체를 참조하면, GC는 해당 static 데이터를 기준으로 참조 체인을 따라가며 어떤 객체를 살아 있는 것으로 판단할지 결정한다.
JNI 에서 생성된 객체
정리.
GC할 때 필요한 요소들
마킹하는 과정
어던 게 루트가 되는지
루트로부터 연결되지 않은 친구는 섬이 된다 -> sweep의 대상이 된다.
sweep후엔 메모리프레그멘테이션을 해결하는 게 중요하다.
그 중 하나가 컴팩트 하는 과정이 있다.
GC알고리즘을 하나씩 알아보자.
화살표가 여러 개의 스레드라고 보면 됨.
Program이 여러개 수행 중인데 전부 멈춰. 하고 GC스레드 나혼자만 돈다.
GC 스레드는 하나뿐이고, 혼자서 여기저기 다 다니면서 마킹하고 sweep하고 compact하는 과정을 진행.
GC 끝났어. 다 이제 돌아! 하면 다른 스레드들이 다시 돌게 되는 구조.
그래서 GC스레드 하나만 돌기 때문에 Serial GC라고 부름.
이미지 출처 : https://memostack.tistory.com/229
Parallel GC는 "모두 멈춰!" 한 다음에, 가용한 여러 개의 스레드를 동시에 활용해 GC를 빠르게 수행하는 방식이다.
예를 들어, 1번 스레드는 루트1번부터, 2번 스레드는 루트2번부터 시작해 마킹 작업을 병렬로 나눠서 처리한다.
이후 살아있지 않은 객체를 각 스레드가 자신이 맡은 메모리 영역에서 나눠서 삭제하고, 압축이 필요한 경우에도 서로 협력해 각자 맡은 영역을 정리한다.
즉, 마킹 → 삭제 → 압축의 전 과정이 여러 스레드에 의해 동시에 분담되고 수행되는 구조이며, 이 병렬 처리를 통해 GC 시간을 줄일 수 있다.
Java 7,8 의 기본 GC이다.
-XX:+UseParallelGC
ParallelOldGC
-XX:+UseParallelOldGC
-XX:+ParallelGCThreads=n
이미지 출처 : https://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf
CMS(Concurrent Mark-Sweep) GC는 Old 영역에서 수행되는 가비지 컬렉터로, Stop-The-World(STW) 시간을 최소화하기 위해 설계되었다.
Mark-Sweep 방식으로 동작하며, Mark 단계를 총 4단계로 나누어 수행한다.
GC Root가 직접 참조하고 있는 객체만 우선적으로 표시(mark)한다.
빠르게 Root 객체만 식별하는 작업이므로 짧은 시간 동안만 애플리케이션을 멈춘다(STW).
Initial Mark에서 찾은 Root 객체를 기준으로, 그와 연결된 객체들을 따라가며 전체 힙에서 참조 가능한 객체들을 표시한다.
이 작업은 GC 스레드가 백그라운드에서 수행하며, 이때도 애플리케이션 스레드는 동시에 실행되므로 STW는 발생하지 않는다.
Concurrent Mark 도중 애플리케이션이 새롭게 객체를 생성하거나 참조를 바꾸는 일이 생길 수 있다.
이 단계에서는 그 변경 사항을 다시 확인하여 마킹 정보를 보정한다.
정확성을 위해 애플리케이션을 다시 멈추지만, 대부분의 마킹 작업이 이미 끝난 상태이기 때문에 STW 시간은 짧다.
마킹되지 않은 객체(즉, 더 이상 사용되지 않는 객체)를 제거(sweep)한다.
이 과정 또한 GC 스레드가 백그라운드에서 실행되며, 애플리케이션은 계속 동작한다.
명시적인 지정 방법
-XX:+UseConcMarkSweepGC
-XX:+CMSInitiatingOccupancyFraction=n
-XX:+CMSIncrementalMode
CMS의 단점인 Fragmentation을 줄이고, GC의 부하를 낮추기 위해 고안된 방법이다.
G1 GC 운영 가이드
G1 GC를 사용할 때, GC 시간이 얼마나 걸리는 것이 적절한지 판단하기 어려울 수 있다.
튜닝이 필요한 상황인지, 코드 리팩토링이 필요한지, 아니면 현재 상태가 괜찮은지 명확한 기준이 없기 때문이다.
일반적으로는 GC가 50ms 이하로 완료되도록 애플리케이션 구조, 코드 작성 방식, 툴 구성 등을 점검하고 조정하는 것이 권장된다.
이 기준을 초과하면 GC 튜닝 또는 구조 개선을 검토해볼 필요가 있다.
이미지 출처 : https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html
G1 GC는 CMS의 단점인 Fragmentation(단편화)을 줄이고, GC 부하를 낮추기 위해 고안된 방식이다.
CMS처럼 빠르게 작동하면서도 Stop-The-World 시간을 줄이고, 단편화도 최소화하고자 메모리 구조 자체를 새롭게 설계한다.
기존처럼 물리적인 Eden, Survivor, Old 영역으로 나누는 대신, 전체 힙 메모리를 바둑판처럼 나눈 블록 단위인 Region으로 쪼갠다.
이 Region은 논리적으로 Eden, Survivor, Old 역할을 부여받는다.
이렇게 하면 특정 블록만 선택해서 GC를 수행할 수 있으므로 성능과 효율이 높아진다.
G1 GC는 전체 메모리를 한 번에 GC하지 않고, Garbage가 가장 많은 Region부터 우선적으로 처리한다.
이를 "Garbage First" 방식이라고 한다.
GC 대상이 되는 Region만 선별하여 처리하기 때문에, 전체를 멈추지 않고도 필요한 영역만 효율적으로 GC할 수 있다.
새로운 객체가 생성되면 Eden Region에 들어간다.
Eden Region이 가득 차면 Minor GC가 발생하고, 이때 Eden과 관련된 일부 Survivor Region만 GC 대상이 된다.
이 방식은 전체 Young 영역을 대상으로 하던 기존 방식보다 훨씬 적은 영역만 GC 대상이 되므로,
Stop-The-World 시간도 짧아진다.
Minor GC가 발생하면 가득 찬 Eden Region만 GC 대상이 된다.
그 Region에서 살아남은 객체는 빈 Survivor Region 또는 Old Region으로 복사된다.
그리고 Eden Region은 비워지고, 필요하면 역할도 바뀐다.
같은 Eden Region이라도 Garbage가 많지 않으면 GC 대상이 되지 않는다.
이런 방식 덕분에 GC 시간도 짧아지고, Stop-The-World 시간도 줄어든다.
GC 과정에서 살아남은 객체를 새로운 Region으로 복사하고, 기존 Region을 비우는 식으로 동작하기 때문에
메모리가 자동으로 정리(Compaction) 된다.
이런 방식은 Fragmentation을 자연스럽게 줄여준다.
하나의 객체 크기가 Region 크기의 50%를 넘으면, 일반 Region에 배치하지 않고
Humongous Region이라는 특수한 Region에 저장한다.
예를 들어, 한 객체가 전체 Region의 80%를 차지한다면 나머지 작은 객체들이 들어오지 못하는 문제가 생길 수 있는데,
이런 상황을 방지하기 위해 따로 분리해서 저장한다.
큰 객체로 인한 단편화 문제를 효과적으로 줄일 수 있는 방식이다.
https://xmlandmore.blogspot.com/2014/11/g1-gc-what-is-to-space-exhausted-in-gc.html
-XX:+UseG1GC
현실적으로 Serial GC는 현대적인 서비스 환경에서 사용이 어렵다.
따라서 선택지는 다음 네 가지다:
이 중에서, 어플리케이션이 일반적인 API 서버나 스트리밍 서비스와 같은 유형이라면 G1 GC 사용을 권장한다.
G1 GC는 다양한 상황에서 안정적인 GC 성능을 제공하며, 대부분의 서비스 환경에서 무난하게 적용할 수 있다.
반면, 대용량 데이터를 처리하거나, OLAP 쿼리 중심의 DB 시스템처럼 복잡한 메모리 접근 패턴이 있는 경우에는
CMS GC가 더 나은 성능을 보이기도 하고, G1 GC가 유리한 경우도 있다.
이러한 경우에는 서비스 특성과 GC 튜닝 결과를 바탕으로 실험적으로 비교 테스트를 해보고 선택하는 것이 좋다.