글 검색

공개된 글의 제목과 본문을 검색합니다.

    닫기 Esc

    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#

    java
    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;
    		}
    }
    • 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 소요시간 등을 보면서 적절히 조정해야할 필요가 있다.
    -ClientClient Hotspot VM으로 구동한다.(Client Class)
    -ServerServer 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:PermSizePermanent Area의 초기 크기를 설정한다.
    -XX:MaxPermSizePermanent 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:+DisableExplicitGCSystem.gc() 함수를 통한 수동 GC를 방지한다.
    • MinHeapFreeRatio와 MaxHeapFreeRatio는 -Xms -Xmx로 지정한 Memory 크기와 같으면 아무런 영향이 없다.

    1.5 Garbage Collection 종류#

    1.5.1 Garbage Collection 관련 옵션#

    Garbage CollectorOptionOld Generation Collection 알고리즘Old Generation Collection 알고리즘
    Serial CollectorXX:+UseSerialGCSerialSerial Mark-Sweep-Compact
    Parallel CollectorXX:+UseParallelGCParallel ScavengeSerial Mark-Sweep-Compact
    Parallel Compacting CollectorXX:+UseParallelOldGCParallel ScavengeSerial Mark-Sweep-Compact
    CMS CollectorXX:+UseConcMarkSweepGCParallelConcurrent Mark-Sweep
    G1 CollectorXX:+UseG1GCSnapshot-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 알고리즘 비교
    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 영역의 단계별 동작 방식
    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 단계
    Mark Phase 단계
    • Reachable Object를 체크하며 Paralllel 작업으로 수행된다.
    • Old 영역의 Region(논리적 구역으로 2KB 정도의 청크)이라는 단위로 균등하게 나눈다.
    • Region이 구분되면 Collection Thread들은 각각 Region별로 Live Object를 마킹한다.
    • 이 때 Live Object의 Size, 위치 정보 등 Region 정보가 갱신되고, 이 정보들은 각 Region별 통계를 낼 때 사용한다.
    Summary Phase 단계
    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 구조 및 튜닝 옵션
    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 영역의 크기가 커진다.
      • 시스템 상황에 적절히 조정해 사용하면 된다.
    전체 글 둘러보기