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 영역의 크기가 커진다.
◦
시스템 상황에 적절히 조정해 사용하면 된다.