////
Search

2. Garbage Collection

Created
2022/09/12 07:06
Tags
JVM

1. GC 소개

1.1 GC 개요

해당 내용은 Hotspot JVM의 Garbage Collection에 대한 기본적인 동작 방식과 Internal 한 내용이 포함되어 있습니다.
이미 할당된 Memory는 Garbage Collection에 의해 해제가 되는데
이때 Garbage는 Heap과 Methode Area에서 사용되지 않는 Obejct를 의미합니다.
소스상의 close()는 Object의 사용중지 의사표현일 뿐 Obejct를 Memory에서 삭제하겠다는 의미는 아니다.
개발자는 System.GC()를 명시적으로 사용하여 GC를 발생시킬 수 있지만 이 경우에는 Full GC가 발생한다.

1.2 GC로 인한 문제점

GC라는 메커니즘으로 인해 직접 메모리를 핸들링할 필요가 없게 되었다.
더불어 잘못된 메모리 접근으로 인한 Crash 현상의 소지도 없어졌으니 매우 편리한 기능이라 할 수 있다.
그러니 GC는 명시적인 메모리 해제보다 느리며 GC 순간 발생하는 Suspend Time으로 인해 다양한 문제를 야기시킨다.

1.3 Root Set과 Garbage

Garbage Collection이란 말 그대로 Garbage를 모으는 작업인데 여기서 Garbage란 사용되지 않는 Object를 말한다.
Object의 사용 여부는 Root Set과의 관계로 판단되어 어떤 식으로든 Reference관계가 있다면 Reachable Object라고 하며 이를 사용하고 있는 Object로 간주한다.
Root Set은 광의적 개념으로서 좀 더 구체적으로 말하면 3가지 참조 형태를 통해서 Reachable Object를 판별한다.
Local variable Section, Operand Stack에 Object의 Reference 정보가 있는 경우
Method Area에 로딩된 클래스 중 constanct pool에 있는 Reference 정보를 토대로 Thread에서 직접 참조하진 않지만 constant pool을 통해 간적 link를 하고 있는 경우
아직 Memory에 남아 있으며 Native Method Area로 넘겨진 Object의 Reference가 JNI 형태로 참조 관계가 있는 Object의 경우
위 3가지 경우를 제외한 경우 모두 GC의 대상이 된다.
GC로 인해서 해제된 메모리 자리에 할당이 새로 이뤄지며 이로 인해 단편화 문제가 발생한다.
이를 해소하기 위해 Compaction 같은 알고리즘을 사용한다.

1.3.1 Reachable but not Live Object

import java.util.*; class Main { public static void main(String args[]) { Leak lk = new Leak(); for(int a=0; a<999999999; a++) { lk.addList(a); lk.removeStr(a); } } } class Leak { ArrayList lst = new ArrayList(); public void addList(int a) { lst.add("가나다라마바사아자차카타파하" + a); } public void removeStr(int i) { Object obj = lst.get(i); obj = null; } }
Java
복사
removeStr() 메소드가 Obejct Reference 변수를 찾아 null로 치환한다.
이 소스를 컴파일하여 실행하면 OOMException이 발생하게 될것이다.
이유는 obj로 받은것이 String이 아닌 String으로 접근하는 Reference값이기 때문이다.
Reference가 null로 치환되었다고 해서 ArrayList에 값이 사라지지는 않는다.
이것이 바로 "Reachable but not Live Object” 인것이다.
이러한 객체가 많아지면 우리는 Heap에 Memory Leak이 발생했다고 표현한다.

1.4 Garbage Collection 목적

GC는 보통 메모리 압박이 있을 때 수행된다.
어떤 이유든지 메모리가 필요하면 수행한다는 의미다.
GC는 새로운 Object의 할당을 위해 한정된 Heap 공간을 재활용하려는 목적으로 수행되는 것이다.
재활용을 위해서 Garbage가 빠져나간 자리는 듬성듬성할 수 밖에 없다.
이 경우 메모리의 개별 Free Space의 크기 보다 큰 Object에게 공간을 할당할 경우 재활용은 의미가 사라진다.
이런 파편화 현상을 방지하기 위해 Compaction과 같은 다양한 알고리즘을 사용한다.

2. Hotspot JVM의 GC

Hotspot JVM은 기본적으로 Generaiton Collection 방식을 사용한다.
Heap을 Object의 Generation별로 Young과 Old 영역으로 구분하여 Young은 Eden과 Survivor 영역으로 구분하여 사용한다.
GC메커니즘은 경험적 지식으로 두 가지 가설을 두고 있는데
첫째로 대부분의 Object는 생성된 후 금방 Garbage가 된다.
둘째로 Older Object가 Younger Object를 참조할 일은 드물다
첫번째 가설을 살펴보면 새로 할당되는 Object가 모인 곳은 단편화 발생 확률이 높다고 간주된다.
Sweep 작업(Mark되지 않은 Object를 제거)을 수행하면 단편화가 발생하며 이후 Compaction 처럼 비산 작업을 해야한다.
때문에 Object할당만을 위한 전용 공간인 Eden Area를 만들고 GC 당시 Live한 Object들을 Survivor 영역으로 피신시키도록 구성한 것이다.
즉 Garbage될 확률이 적은 Object를 따로 관리한다는 것이다.
Garbage를 추적하는 부분은 Tracing 알고리즘을 사용한다.
Root Set에 Reference 관계를 추적하고 Live Object는 Marking한다.
이런 마킹 작업도 Young에 국한 되는데 마킹 작업은 Memory Suspend 상황에서 수행되기 때문이다.
전체 Heap에 대한 마킹 작업은 긴 Suspend Time을 가져갈 것이기 때문이다.
만약 Older Object가 Young Object를 참조하는 상황이 있다고 가정하면 존재 여부 체크를 위해 Old 영역을 모두 찾아 다닐 수 있다.
따라서 Suspend Time도 길어진다.
그렇기 때문에 Hotspot JVM은 Card talbe이란 장치를 마련했다.

1.1 Card table

Card Table이란 Old Generaiton의 Memory를 대표하는 별도 Memoey 구조이다.
Old Generaiton의 일부는 Card Table로 구성되어 있다.
만약 Young 영역의 Object를 참고하는 Old 영역의 Object가 있다면
Old Generaiton Object의 시작주소에 카드(일종의 Flag)를 Dirty로 표시하고 해당 내용을 Card Table에 기록한다.
이후 해당 Reference가 해제되면 표시한 Dirty Card도 사라지게끔 하여 Reference관계를 쉽게 파악할 수 있게 했다.
Minor GC수행 중 Card Talbe의 Dirty Card만 검색하면 빠르게 Reference 관계를 파악할 수 있으므로 위에서 설명한 두 번째 가설을 보완햇다고 할 수 있다.

1.2 TLAB (Thread-Local Allocation Buffers)

GC가 발생하거나 객체가 각 영역에서 다른 영역으로 이동할 때 애플리케이션의 병목이 발생하면서 성능에 영향을 주게된다.
Hotspot JVM은 스레드 로컬 할당버퍼라는 것을 사용한다.
이를 통해 각 스레드별 메모리 버퍼를 사용하면 다른 스레드에 영향을 주지 않는 메모리 할당 작업이 가능하게 된다.
Object 할당은 기존에 할당된 Memory 번지 바로 뒤부터 하게 된다.
이때 Free List 검색 등 부가적인 작업 없이 신속하게 작업이 가능하다.
그러나 반드시 Bump-thre-pointer 같은 기술로 동기화 작업이 수반되어야 한다.
여러 Thread가 최근 할당된 Object 뒤 공간을 동시에 요청하면 동기화 이슈로 문제가 발생한다.
Hotspot JVM은 Bump-thre-pointer기술에 TLAB기술을 추가로 사용하여 Thread 할당 주소 범위를 부여하여 그 범위 내 동기화 작업 없이 빠른 할당이 가능하다.
TLAB를 사용하지 않으면 가장 먼저 요청한 쓰레드가 Heap Lock을 걸고 Allocation을 수행하여 다른 쓰레드는 기다리게 될것이다.
Hotspot JVM은 10개의 Native Instruction만으로 이를 구현하고 있어 성능 측면에서 최적화 하고 있는듯 보인다.

1.3 GC 대상 및 범위

GC 대상 영역은 Young, Old와 Permanent 영역인데 Hotspot JVM은 각 Generation 별로 각각 GC를 수행한다.
Young 영역에서 발생하는 GC를 Minor GC라고 한다.
가장 GC가 빈번하게 수행되는 영역이며 성숙된 Object는 Old Area로 Promotion된다.
Promotion 과정 중 Old 영역에도 메모리가 충분하지 않으면 해당 영역에서도 GC가 발생하는데 이를 가리켜 Full GC(=Major GC)라고 한다.
Permanent Area의 메모리가 부족해도 GC가 발생할 수 있는데 이때 너무 많은 수의 Class Object가 로딩되어 메모리가 부족해로 GC가 발생할 수 있는데
이때는 너무 많은 수의 Class Object가 로딩딩되어 Free Space가 없어졌기 때문이다.
Permanent Area가 부족하면 Heap에 Free Space가 많더라도 Full GC가 발생한다.
Major GC는 Suspend 시간을 더 길게 가져간다.

1.4 GC 관련 옵션들

JVM의 메모리는 각 영역별 메모리 사용 현황이나 GC 횟수, GC 소요시간 등을 보면서 적절히 조정해야할 필요가 있다.
-Client
Client Hotspot VM으로 구동한다.(Client Class)
-Server
Server Hotspot VM으로 구동한다.(Server Class)
-Xms<Size> -Xmx<Size>
Young Generation의 최소 크기를 설정한다. Young Generation의 최대 크기를 설정한다.
-XX:NewSize=<Size>
Intel CPU에서는 default 640kbytes, 그 외에는 2.124Mbytes이다.
-Xss<Size>
Stack 사이즈이다.
-XX:MaxNewSize=<Size>
Young Generation의 최대 크기를 지정한다. 1.4 버전이후 NewRatio에 따라 자동 계산한다.
-XX:PermSize
Permanent Area의 초기 크기를 설정한다.
-XX:MaxPermSize
Permanent Area의 최대 크기를 설정한다. (default=64Mbytes)
-XX:Survivor Ratio=<value>
값이 n 이면 n: 1:1 (Eden:Survivor1:Survivor2)이다. (default=8)
-XX:NewRatio=<value>
값이 n 이면 Young:Old 비율은 1:n Client Class 는 default 8 이다. Server Class는 Default=2 이며 Intel 계열 CPU를 사용하면 default=12이다.
-XX:TargetSurvivorRatio=<value>
Minor GC는 Eden Area 뿐만 아니라 Survivor Area Full 되어도 발생 Survivor Area Minor GC를 유발하는 비율을 말하며 default=50 즉, Survivor Area 가 50% 만 차도 Minor GC 발생한다. 높게 지정하면 Survivor Area 활용도 높아져 Minor GC 발생 빈도를 낮출 수 있다.
-XX:MinHeapFreeRatio=<percent>
전체 Heap 대비 Free Space가 지정된 수치 이하면 Heap 을 -Xmx로 지정된 수치까지 확장한다. (default=40)(늘림)
-XX:MaxHeapFreeRatio=<percent>
전체 Heap 대비 Free Space가 지정수치 이상이면 -Xms까지 축소한다. (default=70)(줄임)
-XX:MaxTenuringThreshold=<value>
Value 만큼 SS1, SS2를 이동하면 Old Generation으로 Promotion 한다.
-XX:+DisableExplicitGC
System.gc() 함수를 통한 수동 GC를 방지한다.
MinHeapFreeRatio와 MaxHeapFreeRatio는 -Xms -Xmx로 지정한 Memory 크기와 같으면 아무런 영향이 없다.

1.5 Garbage Collection 종류

1.5.1 Garbage Collection 관련 옵션

Garbage Collector
Option
Old Generation Collection 알고리즘
Old Generation Collection 알고리즘
Serial Collector
XX:+UseSerialGC
Serial
Serial Mark-Sweep-Compact
Parallel Collector
XX:+UseParallelGC
Parallel Scavenge
Serial Mark-Sweep-Compact
Parallel Compacting Collector
XX:+UseParallelOldGC
Parallel Scavenge
Serial Mark-Sweep-Compact
CMS Collector
XX:+UseConcMarkSweepGC
Parallel
Concurrent Mark-Sweep
G1 Collector
XX:+UseG1GC
Snapshot-At-The-Beginning (SATB)
Snapshot-At-The-Beginning (SATB)
Serial Collector: client class의 기본 collector로 한 개의 thread가 serial로 수행한다.
Parallel Collector: 모든 자원을 투입하여 Garbage Collection을 빨리 끝내는 전략으로 대용량 Heap에 적합하다.
CMS Collector: Suspend Time을 분산시켜 체감 Pause Time을 줄이고자 할 때 사용한다.
Incremental Collector: Low Pause Collector에 속하는 데 현업에서는 거의 사용 안한다. 7에서는 Garbage First Collector(= G1 Collector)를 사용할 수 있는데 (Java6에서도 선택가능) Generation을 물리적으로 구분하지 않고 Region 이라는 단위로 쪼개서 관리를 한다.
G1 Collector는 거의 Real Time에 가깝게 Suspend Time을 감소시킬 수 있지만 아직 안정성이 검증되지 않아서 많은 사이트에서 잘 사용되지는 않는다.
최근 추세로는 Parallel-Concurrent로 수렴되고 있다.

1.5.2 Serial Collector

Young / Old Generation 모두 Serial 로 Single CPU를 사용한다.
1개의 Thread를 가지고 GC를 수행한다.
Client Class이 기본 Collector이며 현재 거의 사용되지 않는 collector이다.

1.5.3 Parallel Collector

이 방식은 Throughtput collector로도 알려진 방식이다.
이 방식의 목표는 다른 CPU가 대기 상태로 남아 있는 것을 최소화 하자는 것이다.
Serial Collector과 달리 Young 영역에서의 콜렉션을 병렬로 처리한다.
이는 많은 CPU를 사용하는 반면에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있다.
멀티 스레드가 동시에 GC를 수행하며 적용범위는 Young영역에 국한된다.
Old영역은 Mark-Sweep-Compact Collection 알고리즘이 사용되며 싱글 스레드 방식이다.
Server Class의 JVM에서 기본 Collector이며 Client Class에서도 옵션 설정으로 사용이 가능하다.
CPU가 한개라면 해당 옵션은 무시된다.
Serial & Parallel Copy 알고리즘 비교
Eden, Survivor 영역의 Live Object Copy 작업을 여러 스레드가 동시 수행한다.
Suspend 현상 발생 = 멈춤 현상
투압한 리소스 만큼 Suspend Time을 단축할 수 있다.
같은 메모리 공간을 두 스레드가 접근하면 Corruption이 발생할 수 있지만 Hotspot JVM은 PLAB라는 Promotion Buffer를 마련해 이런 Corruption을 회피하고 있다.
PLAB(Parallel Allocation Buffer)이란?
GC Thread가 Promotion시 Thread마다 Old Generation의 일정부분을 할당(1024bytes 단위)하고 다 사용하면 다시 Buffer를 재할당한다.
Old Area에 단편화가 발생할 수 있는데 이는 많은 Thread가 자신의 버퍼를 할당 받고 사용하지 않거나 어쩔 수 없이 발생하는 버퍼 내 자투리 공간 때문이다.
Parallel Collector 옵션
Parallel Collector의 기본(Default) 동작 방식은 애플리케이션 수행시간 대비 GC 수행시간은 default 1%이며, 애플리케이션 수행시간의 최대 90%를 넘지 못한다.
또한 Young 영역은 확장할때 20%씩 증가하고 5%씩 감소한다.
-XX:+UseAdaptiveSizePolicy를 사용하면 Heap 크기가 자동으로 설정된다.
GC를 수행하게 되면 반드시 최대 Heap Size 대비 5%의 free 공간을 확보해야 한다.

1.5.4 CMS Collector

이 방식은 low-latency collector로도 알려져 있으며, 힙 메모리 영역의 크기가 클 때 적합하다.
CMS Collector는 Suspend Time을 분산하여 응답시간을 개선한다.
비교적 자원이 여유 있는 상태에서 GC의 Pause Time을 줄이는 목적이고 크기가 큰 라이브 오브젝트가 있는 경우 가장 적합하다.
Old 영역에 Concurrent Mark-Sweep 알고리즘이 사용된다.
Old Area의 Concurrent Mark-Sweep 알고리즘
Old 영역의 단계별 동작 방식
Initial Mark Phase 단계
Single Thread만 사용한다.
애플리케이션이 중지되고 애플리케이션에서 직접 Reference되는 Live Object만 구별한다.
Suspend 상태지만 빠르다.
Concurrent Mark Phase 단계
Single Thread만 사용한다.
애플리케이션은 수행되고 GC Thread 외 Working Thread는 애플리케이션 수행이 가능하다.
이전 단계에서 선별된 Live Object가 Reference하고 있는 Object를 추적해 Live 여부를 구별한다.
Remark Phase 단계
Multi Thread가 사용되며 애플리케이션이 중지된다.
이미 Marking된 오브젝트를 재추적하여 Live 여부를 확정한뒤 모든 리소스를 투입한다.
Concurrent Sweep Phase 단계
Single Thread만 사용한다.
애플리케이션은 수행되고 최종 Live로 판명된 Object를 제외한 Dead Object를 지운다.
Sweep 작업만 하고 Compaction 작업은 수행 안한다.
항상 Compaction 작업은 Heap의 Suspend를 전제로 하는데 반복된 Sweep은 단편화를 유발한다.
때문에 Free List를 사용하여 단편화를 줄이는 노력을 한다
Free List?
Promotion 할당을 할 때 Young 영역에서 승격된 Object와 크기가 비슷한 Old 영역의 Free Space를 Free List에서 탐색하게 된다.
Promotion되는 Object Size를 계속 통계화 하여 Free Memory 블록들을 붙이거나 쪼개서 적절한 크기의 Free Memory Chunk Object를 할당한다.
그러나 이러한 작업은 GC 수행 중 Young 영역에 부담을 주게 되는데 그 이유는 Free List에서 적절한 Chunk 크기를 찾아 Allocation 해야 되기 때문이며 시간도 오래 걸린다.
이 말은 곧 Young 영역의 체류 시간이 길어진다는 의미다.
Promotion이 빈번하지 않다면 Compaction 보다 Free List가 성능적으로 이득이 있다.
CMS Collector의 Floating Garbage 문제
CMS Collector는 단편화 외 Floating Garbage(Garbage면서 수거되지 않고 붕 뜬 Garbnge) 문제가 있다.
요약하자면, 첫 단계에서 마킹된 오브젝트들만 이후 검사 대상으로 이용되기 때문에 다음 단계들을 거치며 남아있는 Garbage를 의미한다.
해당 단계가 시작된 시점에서 해당 문제는 해결이 불가능하고, 다음 GC 작동시에만 해결이 가능하다.
이는 잠재적으로 Old Area를 확장시키게 되는 요인이다.
결론적으로 CMS Collector는 Old 영역에 Object 할당 할 때 Free List를 사용해 단편화를 최소화 하고 Scheduling으로 Remark 단계가 Minor GC 중간 지점에 오도록 조장해 OOME를 방지한다.
Incremental Mode of CMS Collector
CMS Collector는 Pause Time Goal을 가지는데 기본적으로 GC 단계를 세분화해서 Concurrent 한 진행을 도모한다.
Concurrent 작업이 큰 실효가 없으면 GC의 Pause Time 줄이는 효과가 나타나지 않는다.
GC 전 과정 동안 1개의 CPU가 GC Thread에 의해 내내 점유되는데 이러면 Concurrent 작업은 나머지 1개 CPU로 수행하게 되므로 Concurrent의 의미가 퇴색되게 된다.
Incremental 모드를 지원하는데 이는 보다 정교한 Scheduling을 한다.
Concurrent phase를 작은 시간 단위로 쪼개서 Minor GC와 겹치지 않게 한다.
또 Duty cycle을 두어 한 개의 CPU를 점유하는 시간 제한을 둬서 GC의 일량을 조절한다.

1.5.5 Parallel Compaction Collector

Paeallel Collector에서 Old 영역에 새로운 알고리즘(Mark and Compact → Paeallel Compacting)이 추가된 개념으로 Multi CPU에서 유리하다.
Old 영역의 Collection 시간을 감소시켜 효율이 증가하나 몇몇 애플리케이션이 Large System을 공유해 CPU를 확실히 점유 못하면 오히려 제 성능을 발휘하지 못한다.
이 경우 Collection Thread 개수를 줄여서 사용한다.
-XX:ParallelGCThreads=n 옵션
Old 영역의 GC 3단계
Mark Phase 단계
살아 있는 객체를 식별하여 표시해 놓는 단계
Summary Phase 단계
이전에 GC를 수행하여 컴팩션된 영역에 살아 있는 객체의 위치를 조사하는 단계
Compact Pahse 단계
컴팩션을 수행하는 단계로 수행 이 후에는 컴팩션된 영역과 비어 있는 영역으로 나뉜다.
Parallel Compaction 알고리즘 : Old Generation
Mark Phase 단계
Reachable Object를 체크하며 Paralllel 작업으로 수행된다.
Old 영역의 Region(논리적 구역으로 2KB 정도의 청크)이라는 단위로 균등하게 나눈다.
Region이 구분되면 Collection Thread들은 각각 Region별로 Live Object를 마킹한다.
이 때 Live Object의 Size, 위치 정보 등 Region 정보가 갱신되고, 이 정보들은 각 Region별 통계를 낼 때 사용한다.
Summary Phase 단계
하나의 스레드만 GC를 수행하고, Summary 단계가 Mark 단계 결과를 대상으로 작업을 수행한다.
Region 단위이며 GC 스레드는 Region의 통계 정보로 각 Region의 Density를 평가한다.
Density는 각 Region마다 Reachable Object의 밀도를 나타내는데 Density를 바탕으로 Dense prefix를 설정하게 된다.
Dense prefix는 Reachable Object의 대부분을 차지한다고 판단되는 Region이 어디까지인가 구분하는 선이며 더불어 다음 단계의 대상을 구분해 준다.
Dense prefix 왼편에 있는 Region은 이후 GC의 대상에서 제외, 나머지만 컴팩션이 수행된다.
Compaction은 보통 Sliding Compaction을 의미하고, Live Object를 한쪽 방향으로 이동시킨다.
오래된 Object는 더 오래 Heap에 머무를 가능성 크기 때문에 Garbage되지 않을 확률이 높고 그게 왼쪽으로 갈수록 높아진다.
즉 Parallel Compaction Collector는 Region별 Density를 평가해 GC의 범위를 줄여 GC의 수행시간을 줄인다.
Heap을 잠시 정지 상태로 만들고 스레드들이 각 Region을 할당 받아 Compaction을 수행한다.
Compaction작업은 Garbage Object를 Sweep하고 Reachable Object를 왼편으로 몰아넣는 작업이다.
Region마다 배정된 스레드가 Garbage Object를 Sweep한다.

1.5.6 Garbage First Collector

앞으로는 G1 Collector가 CMS Collector를 대체할 전망이다.
CMS Collector에 비해 Pause Time이 개선되었고 예측 가능한 게 장점이다.
Young 영역에 집중하면 효율이 좋으나 CMS처럼 Old 영역의 단편화가 있으며 Free List 사용의 문제점과 Suspend Time이 길어지는 현상 등의 문제점이 있다.
G1은 물리적 Generation 구분을 없애고 전체 Heap을 1Mbytes 단위 Region으로 재편한다.
Region 단위 작업과 Incremental의 Train 알고리즘을 섞어 놓은 느낌이다.
Mark 단계에서 최소한의 Suspend 시간으로 Live Object 골라내는 건 PCC와 비슷하다.
Region별로 순차적인 작업이 진행되고 Remember set을 이용한다.
G1은 Garbage로만 꽉 찬 Region을 발견되자 마자 즉각 Collection 한다.
Garbage Object가 대부분인 Region의 경우 Live Object는 Old Region에서 다른 Old Region으로 컴팩션이 이루어진다.
G1은 Young, Old 영역은 물리적 개념이 아닌 Object가 Allocation되는 Region의 집합을 Young Generation, Promotion되는 Region의 집합을 Old Generation이라 한다.
G1이 Region 내에 Reference를 관리하는 방법은 Remember set을 이용한다.
Remember set은 Region 외부에서 들어오는 참조 정보를 가지고 있다.
Marking 작업시 trace 일량을 줄여줘 GC 효율을 높이게 된다.
G1의 기본적 GC 매커니즘
네모는 전체 Heap, 작은 네모는 Region을 의미한다.
음영이 있는건 Old, 없는건 Young 리전을 의미한다.
3은 새롭게 생성된 Young 리전
4는 승격된 Old 리전이다.
G1은 철저히 리전단위로 GC가 발생하기 때문에 Suspend현상도 해당 Region을 사용하는 Thread에 국한된다.
Garbage First Collector Garbage First Collection
Young CG
GC는 Young 영역에서부터 시작하며 Minor GC와 동일한 개념이다.
Multi 스레드가 작업한다.
Live Object는 Age에 맞게 Survivor 리전, Old 리전으로 Copy되며 기존 공간은 해지된다.
이후 새로운 Object가 할당되는 Young 리전은 Survivor 리전과 그 근처에 비어있는 리전이 된다.
Cuncurrent Mark
Old 영역 GC의 시작
Marking 단계
Single Thread, 전체적으로 Concurrent, 이전 단계 때 변경된 정보 바탕으로 Initial Mark를 빠르게 수행한다.
Remarking 단계
Suspend 있고 전체 Thread가 동시작업, 각 Region마다 Reachable Object의 Density를 계산, 그 후 Garbage Region은 다음 단계로 안 넘어가고 바로 해지된다.
이 단계는 snapshot-at-the-beginning(SATB) Marking 알고리즘을 사용한다.
GC를 시작할 당시 Reference를 기준으로 모든 Live Object의 Reference를 추적하는 방식이다.
Old Region Reclaim - Remarking
Remark 단계
Concurrent 작업, Multi-Thread 방식, GC를 위해 Live Object의 비율이 낮은 몇 개의 Region을 골라낸다.
Evacuation Pause 단계
독특하게 Young Area의 GC를 포함, 앞의 Remark 단계에서 골라낸 Old Region은 Young Region과 같은 식으로 Evacuation 한다.
원 표시의 Region들이 Evacuation의 대상이며 하얀 빗금이 Old Region, 나머지는 Young Region이다.
Young Region은 GC의 첫단계의 결과로 생성된 Survivor Region과 그 근방의 Empty Region 들에 Object가 Allocation되어 생긴 Region이다.
Remark에서 선택된 Old 리전들까지 Evacuation단계에서 같이 Collection된 것이다.
결과로 좌측 상단으로 Survivor Region과 Old Region이 하나 생성된다.
Compaction Phase
다른 Compaction과 달리 Concurrent 작업을 수핸한다.
리전 단위로 작업을 수행하니 가능한 일이다.
컴팩션의 주 목적은 Free Space를 병합해 단편화를 방지하는 것이고, 많은 수의 리전을 균등하게 조금씩 사용하게 되는 부작용을 방지하는 것이다.

1.6 IBM JVM의 GC

1.7 GC 튜닝

JVM Memory 구조 및 튜닝 옵션

1.7.1 GC 튜닝의 필요성

다음과 같은 상황이라면 GC 옵션을 통한 튜닝이 필요하다고 볼 수 있다.
-Xms 옵션과 -Xmx 옵션으로 메모리 크기 설정 없이 사용중이다.
JVM 옵션에 -Server 옵션이 설정되어 있지 않다.
시스템에 Timeout 같은 로그기 발생하면서 정상적인 트랜잭션 처리가 이루어지지 않는다.
GC를 튜닝하는 근본적인 원인
운영 시스템이 GC를 적게 하도록 하려면 개발자에 의해 애플리케이션단에서 Object 생성을 줄이는 작업이 선행되어야 한다.

1.7.2 GC 튜닝 목적

GC 튜닝의 목적은 궁극적으로 애플리케이션의 안정적인 수행을 위해 System의 Suspend Time 감소에 있다.
Old 영역에 넘어가는 Object의 수를 최소화하고, Full GC의 시간을 줄이는 것이다.

1.7.3 Object 수 최소화의 중요성

JDK 7부터 본격적으로 사용할 수 있는 G1 Collector를 제외하고 Hotspot JVM에서 제공하는 모든 GC는 세대별 GC다.
Eden과 Survivor을 오가다가 끝까지 남아 있는 Object는 Old 영역으로 이동한다.
간혹 Eden에서 만들어지다가 크기가 커져 Old로 바로 넘어가는 Object도 있긴 하다.
Old의 GC는 Young 영역의 GC에 비하면 상대적으로 시간이 오래 소요되기 때문에 Old 영억으로 이동하는 Object의 수를 줄이면 Full GC가 발생하는 빈도를 많이 줄일 수 있다.

1.7.4 Full GC Time 줄이기

Minor GC에 비해 Full GC는 시간이 상대적으로 더 오래 걸린다.
Full GC 실행에 시간이 오래 소요되면 연계된 여러 부분에서 타임아웃이 발생할 수 있다.
Old 영역의 크기를 줄이면 OOME가 발생할 수 있다.
반대로 늘려버리면 GC의 횟수는 줄어들지만 실행 시간이 늘어나게 된다.
적절히 Old 영역 메모리 크기를 설정하는게 좋다.

1.7.5 GC의 성능을 결정하는 옵션

기본적으로 -Xms, -Xmx, -XX:NewRatio 옵션을 사용해야 한다.
특히 -Xms, -Xmx은 필수로 지정해야 하는 옵션이다.
-XX:NewRatio옵션을 어떻게 설정하느냐에 따라 Permanent 영역의 크기가 조정되고 따라서 GC 성능에도 많은 차이가 발생한다.
Perm 영역의 크기는 OOME가 발생하고, 그 원인이 Perm 영역의 크기 때문일 때에만 -XX:PermSize 옵션과 -XX:MaxPermSize 옵션으로 지정해도 큰 문제는 없다.
GC 옵션은 두 대 이상의 서버에서 서로 다르게 설정해 바교해 성능 개선 효과가 있는 옵션으로 최적화 하는게 맞다.

1.7.6 GC 튜닝 과정

GC 지표 모니터링
가장 쉽게 GC 상황을 모니터링 하는 방법은 jstate 명령어를 활용하는 방법이다.
해당 명령으로 파일에 로그 내역을 쌓게하여 추이를 관찰할 수 있다.
-verbosegc, -Xloggc:<파일위치> 옵션을 이용해 JVM에서 로그를 받아볼 수 있다.
또는 APM을 이용해 GC 모니터링이 가능하다.
모니터링 결과 분석 후 GC 튜닝 여부 결정
GC 옵션을 지정해 적어도 하루~이틀 이상 데이터를 수집해 분석을 진행한다.
GC 지표 및 로그를 분석해 메모리가 어떻게 할당되는지 확인해 GC 방식과 메모리 크리를 변경해 가면서 최적의 옵션을 찾아 나가야 한다.

1.7.7 GC튜닝이 불필요한 상황

Minor GC의 처리 시간이 50ms 내외로 빠른 경우
Minor GC 주기가 10초 내외로 빈번하지 않은 경우
Full GC의 처리 시간이 보통 1초 이내로 빠른 경우
Full GC 주기가 10분에 1회 정도로 빈번하지 않을 경우
Minor, Full GC의 시간만으로 판단해서는 안되고 GC가 수행되는 횟수도 중요한 포인트 중 하나이기 때문에 이 부분도 확인해야 한다.

1.7.8 GC 방식 선택

GC 방식은 Hotspot JVM을 기준으로 총 5가지가 있다.
이중 어떤 방식을 선택해야한다는 원칙은 없지만 가장 좋은 방법은 Parallel GC, Parallel Compacting GC, CMS GC의 3가지를 다 적용해 보는것
한가지 확실한 것은 CMS GC가 다른 병렬 GC보다 바르다는 것이다.
하지만 CMS GC가 항상 빠른것은 아니다.
Concurrent mode faulure가 발생하면 다른 병렬적 GC보다 느리다.
Parallel GC 방식에서 Full GC가 수행될 대마다 컴팩션 작업을 진행하기 때문에 시간이 많이 소요된다.
벗뜨, Full GC가 수행된 이후 메모리를 연속적으로 지정가능해 더 빠르게 할당 가능하다!
CMS GC는 컴팩션을 기본적으로 수행하지 않기 때문에 속도가 빠르지만, 메모리 파편화가 발생한다.
때문에 Parallel GC보다 문제가 될 수 있다.
결론적으로 운영 중인 시스템에 가장 적합한 GC를 찾아야 한다.
운영 특성에 맞춰 적절히 선택하자

1.7.9 Memory 크기와 GC 상관 관계

메모리 크기가 크면 GC 발생 횟수는 줄어들고 GC 수행 시간은 증가한다.
메모리 크기가 작으면 GC 수행시간은 줄어들고 GC 발생 횟수는 증가한다.
Full GC가 1~2초 내에 끝난다면 10GB로 지정해도 무관하다.
대부분 서버는 그렇지 못한다.
Full GC 이후 사용중인 메모리가 300M 정도라면 300M + 500M(Old 영역용) + 200M(여유 메모리)를 감안해 1G 정도로 지정하는 것을 권장한다.
메모리 크기를 설정할 때 고려하는 추가지표는 바로 NewRatio다.
NewRatio는 Young과 Old의 비율을 의미한다.
-XX:NewRatio을 1로 설정하면 비율은 1:1이 된다.
-XX:NewRatio가 2라면 1:2가 된다.
즉 값이 커지면 커질수록 Old 영역의 크기가 커진다.
시스템 상황에 적절히 조정해 사용하면 된다.