대메뉴 바로가기 본문 바로가기

데이터 기술 자료

데이터 기술 자료 상세보기
제목 IBM JVM GC
등록일 조회수 6955
첨부파일  

IBM JVM GC



㈜엑셈 컨설팅본부 /APM팀 김 정태

개요

본 문서는 IBM JVM의 Memory 구조 및 Garbage Collection 동작원리에 대한 내용으로 이루어져 있다. 본문 내용을 통해 Garbage Collection으로 인한 Suspend 현상과 Application Thread, Garbage Collection Thread간의 경합에 대해 이해하고 나아가 특정 환경에 적합한 Garbage Collector를 선택할 수 있는 지식을 제공할 목적으로 작성되었다.


1. IBM JVM의 Heap Memory 구조

Hotspot JVM은 Generational Heap 구조로써 각 세대별(New, Old, Permanent)로 Memory 영역이 분리되어 있다. 즉 생성된 Object가 젊은 것들은 New, 늙은 것들은 Old, Object가 Instance화 되기 위해 참고되는 Class 메타 정보들은 Permanent Area에 할당된다. 또한 Garbage Collector는 이러한 영역별로 각기 다른 방식의 Garbage Collection을 수행한다. 반면 IBM JVM은 기본적으로 One Heap 구조이며, Java 1.5 버전부터는 Hotspot JVM과 같이 Generational Heap 구조를 사용할 수도 있다.


1.1 IBM JVM Heap 영역별 설명 (Java 1.4 이하)

One Heap 구조에서는 Heapbase (Heap Memory의 처음 주소)에서 Heaptop 까지 확장이 가능하며 Heaplimit은 Heap의 최소 크기를 의미한다. Heapbase ~ Heaptop 까지는 -Xmx 옵션으로 설정되는 값이며, -Xms 옵션으로 초기 JVM의 Heap Size를 설정할 수 있다. 그리고 Heaplimit은 .Xms로 설정된 값 밑으로 내려올 수 없다.


1.1.1 K Cluster, P Cluster

Pinned Class Object(K Cluster), Pinned Object(P Cluster)가 할당되는 곳으로 Default 1280개의 Class Entries가 저장 가능하며, Hotspot JVM의 Permanent Area와 비슷한 역할을 한다. K Cluster가 꽉 차면 P Cluster에 할당되며 P Cluster가 꽉 차게 되면 2kbyte의 새로운 P Cluster가 Heap의 영역에 임의적으로 생성되는데 이로 인해 Heap Fragmentation이 발생하기도 한다.


1.1.2 Heap

생성된 Object들이 할당되는 영역이다.


1.1.3 Cache

Cache Allocation을 위한 THL(Thread Local Heap)과 512byte 이하의 작은 Object를 할당하는데 사용되는 영역이다. THL은 Heap Lock을 회피하는 데 사용되는데 THL에 대한 자세한 내용은 뒤에서 자세히 다루겠다.


1.1.4 Wilderness OR LOA

Large Object의 할당을 효과적으로 하기 위한 공간으로 주로 64kbyte의 Object 할당에 사용된다. 항상 64kbyte 이상의 Object가 할당되는 것이 아니라 Heap Fragmentation등의 이유로 Heap영역에 Object 할당이 불가능할 경우 64kbyte보다 작은 일반 Object들이 할당되기도 한다.


1.2 Java 1.5 버전부터 변경된 Heap 구조

Java 1.5 버전에서는 위와 같이 Pinned Cluster Area(K Cluster, P Cluster )가 사라졌다. 이렇게 바뀌게 된 이유는 Pinned Object로 인한 Heap Fragmentation 문제 때문이다. Pinned Object란 고정된 Object라는 것으로 원칙적으로 Java에서 모든 Object들은 Garbage Collector를 통해 Heap 내에서 이동이 가능하도록 되어 있다. 그러나 일부 특수한 경우 Garbage Collector가 이동시키지 못하는 Object들이 있다. 예들 들어 JNI(Java Native Interface)에서 사용하는 Object들은 JNI에서 직접 Unpinned 될 때까지 고정이 된다. 또한 Class에 대한 메타 정보들도 고정 영역으로 지정된다. 그러다 보니 실제 Pinned Object들은 GC에 의한 Compaction 작업이 불가능하다. 이러한 이유로 Pinned cluster라는 영역을 두어 해당 영역에 Pinned Object들을 따로 저장하는 것이다.

문제는 이러한 Pinned cluster 영역 보다 많은 Pinned Object가 발생하게 되면 Pinned Object를 저장하기 위해 Heap 영역에 Pinned Object를 위한 공간을 임의로 할당한다는 것이다. 이러한 공간은 임시로 존재할 수도 있고 영구적인 공간이 될 수도 있다. 이런 현상이 발생하게 되면 Heap에 한번에 할당할 수 있는 일련의 Memory가 점점 줄어들게 되어 나중에는 별로 크지 않는 Object를 할당하려고 해도 Allocation failure(이하 AF)가 발생하게 된다. Java 1.5 버전에서는 이러한 문제점을 해결하기 위해 Pinned Object의 저장을 JVM Heap이 아닌 System Heap에 저장하도록 변경하였다. IBM에서 제공하는 Diagnostic 1.4 문서를 보면 "Avoid Fragmentation" 파트를 통해 fragmentation에 대한 부분을 다루고 있으나, 1.5 문서에서는 해당 내용이 삭제되었다. 이를 통해 암시적으로 IBM JVM에서 Fragmentation 문제를 해결했다는 걸 알 수 있다. 참고로 1.4 버전에서 발생하는 Heap Fragmentation 문제는 K Cluster와 P Cluster의 크기를 적절히 조정(-Xk, -Xp 옵션 사용)함으로서 해결할 수 있다.



1.3 Heap Allocation

IBM JVM은 생성되는 Object를 위한 Heap을 할당 받을 때 Memory Corruption 방지를 위해 기본적으로 Heap Lock을 사용한다. 좀 더 정확히 이야기하면 512 Bytes 이상의 Object를 Heap에 할당할 때는 Heap 전체에 Lock을 건 후 Free List를 탐색한다. 이후 Free Chunk를 획득하면 Heap Lock을 해제, Free Chunk 못 찾으면 GC 수행 후 다시 Free Chunk 획득을 시도한다. 이때 Free Chunk 를 찾는 법은 First fit 방식이다. 즉 Free List에서 찾은 Free Chunk가 Object의 크기보다 커도 일단 할당하고 Object 만큼 사용되고 남는 부분 Free List로 반환을 한다. 단 남은 공간이 512Bytes 이하면 반납을 안 하는데 이 공간을 Dark matter라고 하며 GC중 Compaction단계에서 제거되게 된다. Object 크기가 512 Bytes라면 Heap cache(=Thread local Heap(이하 TLH))영역에 할당한다.

TLH는 Hotspot JVM의 TLAB과 같은 기능으로 각 Thread 마다 배타적 공간을 할당하여 빠른 Allocation이 가능하도록 하는 장치이다. 이런 이유로 512Mbyte 이상의 Object를 할당할 때에도 Cache영역에 할당하여 TLH를 이용하기도 하며 TLH를 이용하면 Heap Lock 은 사용하지 않는다.

추가로 IBM JVM은 GC시 빠른 Mark를 위해 Allocbits, Mark bits를 사용한다. Allocbits는 할당을 표시하는 Bit vector 라고 할 수 있는데 TLH를 사용할 때 Allocbits에 기록되는 시점은 TLH가 Full 되거나 GC 발생 직전으로 Thread에게 TLH 할당할 때가 아니다. Allocbits, Mark bits는 뒤에서 자세히 다루겠다.


2. IBM JVM Garbage Collection

IBM JVM의 메모리 구조와 Object 할당에 대한 기본적인 내용을 살펴보았다. 본격적으로 IBM JVM Garbage Collection에 대해 이야기해보자. IBM JVM이 제공하는 Collector는 기본적으로 Young/Old Generation의 구분을 사용하지 않기 때문에 Minor GC(Copy), Major GC(mark and sweep)와 같은 구분 또한 존재하지 않는다. 대신 Memory를 정리하는 일련의 과정을 Mark and Sweep + Compaction으로 구분한다. Mark and Sweep 단계는 ‘1분기 White Paper : Sun JVM GC문서’에서 이미 언급한 바 있다. 이른바 Stop the World 작업으로 Application Thread를 멈춘 상태에서 Alive Object를 Mark하고(Dead Object를 찾고) Collection하는 작업을 한다. 그런데 IBM JVM은 Mark and Sweep 단계를 매우 "가벼운" 작업으로 간주한다. 비록 이 단계가 Sun JVM의 Major GC와 비슷한 속성을 지니고 있지만 Major GC에 비해서는 단순한 작업으로 구현되어 있다.

Sun JVM의 Major GC와 비슷한 작업은 Compaction 단계에서 발생한다. Mark and Sweep으로 Memory 정리를 한 후에도 Object 할당에 필요한 여유 Memory를 찾지 못하면 Compaction, 즉 고비용의 압축작업이 발생한다. Mark and Sweep 작업이 단순히 Dead Object를 정리하는 작업인데 반해 Compaction 작업은 흩어진 Free Memory를 합치는 작업을 하기 때문에 Mark and Sweep 단계에 비해 많은 시간을 필요로 한다. 다행히 일반적으로 Compaction 작업은 드물게 발생한다. Mark and Sweep 단계를 거쳤음에도 불구하고 Object 할당에 필요한 Memory를 찾지 못하는 이유는 단편화(Fragmentation)에 있다. IBM JVM의 Mark and Sweep은 Compaction 작업을 하지 않기 때문에 Free Memory가 여기 저기에 흩어지는 현상이 생기게 된다 가령 1M의 Free Memory가 연속되지 않은 10K Chunk 100개로 흩어져 있다고 가정해보자. 이 경우 비록 총 1M의 Free Memory가 존재하지만 20K의 연속된 Memory는 할당될 수 없다. 이런 경우에는 Compaction 작업이 추가로 발생하게 된다.

Hotspot JVM에서는 Major GC를 최적화하는 것이 Heap 튜닝의 대표적인 기법이듯이, IBM JVM에서는 Compaction을 줄이는 것이 Heap 튜닝의 대표적인 기법이다.

참고로 IBM 이 제시하는 Heap 튜닝 기법 중 하나가 길이가 긴 Array를 사용하지 말라는 것이다. 이 기법의 근거가 바로 Heap의 단편화에 있다. Application이 일정 시간 동작하고 나면 필연적으로 단편화가 발생한다. 이럴 때 길이가 긴 Array를 사용하면 연속된 일련의 Memory 공간을 할당 받지 못해 Compaction 작업이 발생하게 된다. 따라서 효과적인 자료 구조를 사용해서 작은 크기의 Array를 여러 개 사용할 것을 권장한다.


2.1 Global GC와 Scavenger GC

IBM JVM에서는 Minor GC와 Major GC라는 분류법은 존재하지 않는다. 대신 Global GC와 Scavenger GC라는 분류법이 존재한다. Throughput 최적화 Collector와 Response Time 최적화 Collector가 행하는 GC는 무조건 Global GC이다. 즉 Sun JVM의 관점에서 보면 항상 Major GC를 수행하고 있는 셈이다. 하지만 Compaction이 일어날 때만 진정한 의미에서 Major GC와 같다고 할 수 있다. IBM JDK 1.5에서 추가된 Generational Concurrent Collector에서는 Scavenger GC가 Minor GC의 역할을 하고 Global GC가 Major GC의 역할을 한다. Scavenger GC는 Mark and Sweep 방식이 아닌 Copy 방식(Alive Object를 Allocate Space에서 Survivor Space로 복사하는 것을 의미)을 사용하며 Global GC에서 Mark and Sweep + Compaction을 사용한다.


3. Garbage Collection 단계

IBM JVM에서 제공하는 각 Collector의 특징을 알아보긴 전 Mark, Sweep, Compaction 단계에 대해 구체적으로 알아보자.


3.1 Mark 단계

Martk 단계는 모든 Live Object들에 대한 Mark 작업을 하는 단계로서 참조계수(Reference count)가 1이상인 Object는 Mark 되며 나머지는 Garbage로 간주된다. Mark된 Live Object는 Markbit vector에 주소가 저장된다. Parallel Mark 기능을 사용할 수 있으며, 다수개의 Thread가 Mark 단계를 수행하기 위해 기동된다. 이렇게 Parallel Mark를 위해서는 - Xgcthreads 옵션을 사용하면 된다.


3.2 Sweep 단계

Mark 단계에서 Live Object의 주소 저장소인 Markbit vector와 모든 Allocation Object의 주소 저장소인 Allocbit vector와 교차 비교하여 Live Object외 Garbage Object를 삭제하는 작업을 수행한다. 이로 인해 Parallel Bitwise Sweep 기능을 사용할 수 있으며 Parallel Mark와 같이 다수개의 Thread가 Sweep단계를 수행하기 위해 기동할 수 있다. 이러한 Parallel sweep Thread수는 -Xgcthreads 옵션으로 설정이 가능하다.


3.3 Compaction 단계

Sweep 단계에서 Garbage Object들이 삭제되고 나면, Compaction 단계에서는 Heap상의 삭제된 Garbage Object들의 공간에 대해 재정렬 작업을 수행한다. 이러한 Compaction 작업을 수행함으로써 연속된 Free Memory 공간을 확보할 수 있다. Compaction 작업은 Object들이 Heap Memory상에서 이동이 발생하며 이 경우 모든 Reference의 변경이 발생할 수 있어 수행 시간이 오래 걸린다. Compaction 작업은 -Xnocompactgc 이 설정되어 있지 않고 다음과 같은 상황일 경우 발생할 수 있다.

Compaction 작업이 발생하는 경우

"-Xcompactgc"가 설정된 경우
Sweep 단계 이후 Object 할당을 위한 Free space가 충분하지 않을 경우
-Xcompactexplicitgc옵션이 설정된 상황에서 System.gc()가 호출되었을 경우
적어도 이전에 가용한 Memory의 절반이 TLH Allocation에 사용되었고, 평균 THL 크기가 1024byte 아래로 떨어질 경우
가용한 Heap이 5%이하일 경우
가용한 Heap이 128kb 이하일 경우



4. IBM JVM Collector

본격적으로 IBM JVM의 Collector에 대해 다뤄보자.

Throughput 최적화 Collector(optthruput)는 말 그대로 Mark and Sweep + Compaction 기법을 사용한다. 주기적으로 Mark and Sweep이 발생하고 필요한 경우 Compaction을 통해 Memory 병합을 시도한다. 이러한 일련의 GC 작업은 Application Thread를 완전히 멈춘 상태에서 진행되기 때문에 GC 작업 자체는 가장 최적화된다. 그 만큼 처리량은 높아지지만 Pause Time이 길어지면서 Application의 Response Time이 길어지는 단점이 있다.

Response Time 최적화 Collector(optavgpause)는 Mark and Sweep + Compaction 기법에 약간의 변화를 가한다. Mark and Sweep 단계를 되도록이면 Application Thread를 멈추지 않는 상태에서 Concurrent 하게 진행한다. 즉 Concurrent Mark 단계와 Concurrent Sweep 단계를 추가로 두어서 Mark and Sweep에 의한 Pause Time을 최소화한다. Concurrent Mark와 Concurrent Sweep 단계는 Application Thread와 같이 동작하며, 그 만큼 Application Thread의 CPU 자원을 소모한다. 따라서 Throughput 최적화 Collector에 비해 Throughput이 다소 떨어질 수 있다.

Generational Concurrent Collector(gencon) 는 Hotspot JVM처럼 Heap을 Generational Heap으로 분리하여 관리한다. 이로 인해 Throughput 최적화 Collector와 Response Time 최적화 Collector의 특징을 모두 가지고 있다. 뒤에서 자세히 살펴보자. Subpool Collector(subpool)는 16개 이상 CPU를 보유한 SMP(Symmetric Multiprocessing) Machine에 적합하다. 이 Collector는 Collector로서 특정보다 Heap layout의 효율성이 강조된다.


4.1 IBM JVM의 기본적인 GC 관련 옵션

4.2 Throughput 최적화 Collector

기본적으로 Heap에 Object 할당 작업이 실패하면 GC가 발생하며 전 과정 모두 Parallel이며 전 과정 모두 Suspend 한 상태에서 GC를 수행한다. Compaction 작업은 AF가 발생할 때만 수행한다. Mark 단계에서 Garbage Object 구별, Sweep단계에서 Mark된 Object를 Sweep 후 할당에 실패한 Object에 대해 다시 할당시도를 한다. 그러나 Free Chunk보다 Object가 커서 할당이 불가능하다면(단편화에 의한 AF발생) Object할당을 보류한 후 연이어 Compaction 작업을 수행한다. Mark 단계 후 Sweep 단계는 연이어 수행되는데 이는 모든 IBM JVM의 Collector에서 동일하다. 중요한 점은 AF 발생 시 Compaction 단계를 수행한다는 것이다.


4.2.1 Throughput 최적화 Collector의 옵션

4.3 Response Time 최적화 Collector

하나이상의 Background Thread가 GC를 수행한다. Application 수행 도중 Concurrent Mark, Concurrent Sweep를 수행한다. Pause time을 줄이기 위해 Concurrent Mark를 수행하지만 Stop-the-world Mark 도 같이 수행한다. 그러나 Concurrent중 Garbage되어버리는 Floating Garbage문제는 남아있다. (Floating Garbage는 다음 GC에서 사라진다. Floating Garbage에 대한 내용은 1분기 Hotspot JVM GC 문서를 참고하기 바란다.) 전체 Thread가 Suspend 되는 경우는 Stop-the-world Mark 단계와 AF가 발생했을 때, 그리고 system.gc()가 호출했을 때이다.


4.3.1 Response Time 최적화 Collector의 옵션

4.4 Generational Concurrent Collector

Generational Concurrent Collector를 선택하면 기존의 Heap구조에서 아래와 같이 Generational Heap 구조로변경된다.



Nursery Area중 Allocation Space는 Object가 할당되는 공간인데 이 공간에서 AF가 발생하면 Scavenge Copy 가 발생한다. Scavenge Copy는 Garbage들 사이에서 Live Object를 찾아 Survivor Space로 Object를 옮기는 작업을 말한다. Scavenge Copy 때 성숙된 Object가 있으면 Tenured Area로 승격된다. Hotspot처럼 GC가 Nursery와 Tenured 각각 별개로 진행된다. Nursery에는 Scavenge Copy가 짧고 반복적으로 수행되고 Tenured Area는 Garbage Object만 골라 처리하는 GC방식이 사용된다.



Scavenge Copy는 Nursery Area의 Heap Suspend 상태에서 매우 짧게 자주 수행되며 Concurrent Mark는 Response Time 최적화 Collector의 Concurrent Mark방식과 동일하다. 위 그림은 Scavenge Copy완료 후 어느 시점부터 Concurrent Mark가 수행되는 데 그 와중에 다시 Scavenge Copy가 수행되는 걸 표현하고 있다. 그러나 Concurrent Mark는 완료가 안 됐으므로 계속 Mark작업을 한다. 그러다가 Concurrent Mark가 완료되면 Global Collection이 수행된다. Global Collection은 보통 Sweep작업만 수행하는 데 경우에 따라 Compaction도 수행된다.

Scavenge 단계에서 Nursery 영역의 Allocate space와 Survivor space를 이용하여 GC를 수행한다. 그 방식은 Allocate space가 가득 찰 경우 GC가 발생되며 Allocate space의 Garbage는 삭제되며, Live Object는 Survivor space로 복사된다. 이러한 단계를 여러 번 거치면서 Live Object는 Tenured space로 옮겨지게 된다. Sun 계열의 Minor GC와 굉장히 유사하다는 것을 알 수 있다. 그러나 Sun 계열의 옵션인 -XX:MaxTenuringThreshold 같은 옵션에 대한 내용은 기술 문서에서 찾을 수가 없었다.

JVM이 Logic을 처리하는 동안, 새로운 Object들은 당연히 Nursery영역에 생기게 된다. 만일 새로운Object가 할당되는데 필요한 연속된 Memory 영역이 부족하게 되면 객체는 Tenured영역으로 옮겨지게 된다. 또한 Garbage collection 발생시 옮겨지기도 한다. Nursery 영역은 Allocate Space와 Survivor Space로 나뉘게 된다. Object는 최초 Allocate Space에 생기게 된다. 만일 Space가 부족하게 되는 경우 Allocation Failure가 발생되면 Garbage Collector가 작동되고 Scavenge가 시작된다. Scavenge가 진행되는 동안 Reference가 살아있는 Object들은 Survivor Space로 이동되고 이때 Reference가 없는 Object들은 건들지 않는다. Reference가 살아있는 Object들의 이동작업이 다 끝나게 되면 Allocate Space와 Survivor Space는 그 역할을 순간적을 바꾸게 된다. 이때 기존의 Reference가 없는 Object들은 순간적으로 비워지게 된다. 그리고 다음 Scavenge가 발생할 때 까지는 Survivor Space가 Object를 위한 Allocate 공간 업무를 수행하게 된다.


4.4.1 Nursery Area의 Collection : Scavenge Copy Garbage Collection

AF가 발생하면 모든 Thread는 작업이 멈춘다. 그리고 Allocation Area에서 Live Object 찾아내 이를 Survivor Area로 복제한다. 그리고 Scavenge Copy때 14 Aging 되는 Object는 Tenured Area로 승격된다.(참고로 Hotspot은 Age최댓값이 31, IBM은 14이다.) Aging은 한번의 Scavenge Copy에서 살아남을 때마다 1씩 증가한다. 이후 Allocation Space가 Survivor Space가 되고, Survivor Space는 Allocation Space가 됨(Flip된다고 표현함)



좀 더 자세히 살펴보면 Nursery Area의 할당 작업 최적화를 위해 내부적으로 Tilting기술이 적용됐다. Tilting 기술은 Scavenge Copy를 통해 이동하는 Reachable Object의 양으로 Survivor, Allocation Area의 크기가 자동으로 결정되는 기술을 말한다. 처음은 Allocation Area와 Survivor Area 같은 크기지만 Scavenge Copy 이후 Survivor Object의 양이 충분하지 않음을 인지한 후 비율이 80%로 증가된 모습을 확인할 수 있다.


4.4.2 Tenured Area의 Collection : Concurrent Mark와 Global Collection

Tenured Area에 GC가 발생하면 Concurrent Mark작업 수행이 수행된다. 이때는 Pause Time Collector처럼 Concurrent Mark, Stop-the-world Mark 단계로 진행된다. Application을 수행하면서 Background로 Marking작업을 수행하며 작업이 완료되면 Generational Concurrent Collector는 모든 Application작업을 멈추고 Remark, Sweep 작업을 수행한다. 이후 Global Collection 수행하는데 보통 Sweep만 하지만 경우에 따라 Compaction작업도 수행한다. Sweep작업은 Pause time Collector처럼 Concurrent Sweep은 아니고 Parallel bitwise Sweep방식을 사용한다.


4.4.3 Generational Concurrent Collector의 옵션

4.4.4 Generational Concurrent Collector의 장단점과 옵션 적용 시 주의사항

Generational Concurrent Collector의 장점은 Application의 전반적인 응답속도(Response Time)가 향상하는 효과를 가져올 수 있다. 단점은 Concurrent한 Thread 기동에 의해 실제 Application 수행에 있어서의 Throughput이 감소한다. 그리고 디폴트 Collector보다 CPU 사용률이 다소 높을 수 있다.

Generational Concurrent Collector는 앞서 설명했듯이 Java 1.5 이상부터 적용이 가능하다. 그리고 무엇보다 Nursery의 두 영역 중 한 곳은 항상 비워져 있으므로 OOME가 빈번한 JVM이라면 JVM Memory 사이즈를 조금 더 크게 설정할 필요가 있다.


4.5 Sub Pool Collector

16개 이상 CPU를 보유한 SMP(Symmetric Multiprocessing) Machine에 적합하다. 이 Collector는 Collector로서 특정보다 Heap layout자체 효율성이 부각되어 있다. Sub Pool은 저장될 Object size를 기준으로 Free List를 각각 구성해 비슷한 크기의 Free Chunk의 Pool로 Heap을 편성한다. 때문에 Allocation과정에서 Heap락이 분산되어 할당이 더욱 빠른 것이다. 또 비슷한 크기의 Object가 생성, 삭제되므로 Large Object에 의한 AF현상도 줄어들게 된다.



Heap 자체는 One Heap을 고수하지만 Pool이라는 자료구조를 활용해 Free List가 여러 개 생성된다. Pool은 Free Chunk의 크기에 따라 구성된 Free List를 오름차순으로 가리킨다. Free List는 One Heap에서 Free Chunk들의 주소를 보유한다. GC는 Optimize for throughput Collector와 동일한 방식을 사용한다. 즉 Mark단계에서는 Parallel Mark, Sweep단계에서는 Parallel bitwise Sweep, Compaction단계에서는 Parallel Compaction을 사용한다.

Sub Pool Collector를 선택해도 JVM기동직 후 처음부터 Free List가 분리된 Sub Pool구조가 만들어지는 건 아니다. 하나의 Free List로 작업을 수행하다가 첫 GC가 수행되어 Sweep 단계를 완료하게 되면 Sub Pool특징들이 나타나기 시작한다. Compaction작업이 수행되면 Sub Pool구조가 해체되고 하나의 Free List로 회귀, Sweep 단계가 수행되면 다시 Free Chunk의 크기 별 Free List가 생성된다. Sub Pool이 Sweep단계에서 재 구성되는 이유는 Sweep단계의 결과물이 Free Chunk를 대상으로 생성된 Free List이기 때문이다. Compaction단계에서는 Free Chunk들이 해체되고 Free List를 크기 별로 재 배열하는 것 보다 다시 처음부터 시작 하는 게 성능상 유리하다고 한다.


4.5.1 Sub Pool Collector의 옵션

4.5.2 SMP란

두 개 이상 프로세서가 한 개의 공유메모리를 사용하는 다중 프로세서 컴퓨터 아키텍처로, 현재 사용되는 대부분 다중 프로세서 시스템은 SMP 아키텍처를 따른다. 향후 SMP는 NUMA(Non- Uniform Memory Access) 기술로 발전된다.


5. IBM JVM환경에서 발생할 수 있는 Memory Leak에 대한 일반적인 유형

먼저 GC 의 알고리즘을 다시 한번 상기시켜 보자 . GC 가 Trigging 되면 Garbage Collector 는 Heap 공간 전체를 Scan 하여 현재 ' 사용 중 ' 이거나 ' 살아있는 ' 객체들을 표시 (Mark) 한다 . 이들 을 Reachable Objects 라고 표현하며 반대로 사용이 끝났거나 Reference 가 끊긴 객체들을 Unreachable Objects 라고 표현한다 . 물론 이들은 GC 에 의해 수거될 대상이다 . 여기서 Reachable Objects 중 아래의 경우를 생각해 보자 .

A 라는 객체가 B 라는 객체를 호출하거나 생성하여 Reference 를 만들었다 . 이후 A 객체는 아 무 일을 하지 않더라도 Unreachable Objects 가 아닌 Reachable Objects 로 분류된다는 점이다 . 이러한 관점으로 일반적인 Memory Leak 요소가 될 수 있는 사항은 다음과 같다 .

. Hash table 에 담긴 (Hashed) 객체들은 명시적으로 제거되지 않는 한 GC 의 대상이 되지 않 는다 . Hashed Object 들은 항상 Reachable Objects 로 분류되기 때문이다 .

. Static Class Data 는 일반 객체들과 독립적으로 존재한다 . 좀 더 자세히 설명하면 Static Class 및 Static Variable 은 Permanent Area 에 존재한다 .

. JNI Reference 는 사용 후 반드시 명시적으로 제거해야 한다 .

. Finalize Method 를 가지고 있는 객체들은 Finalizer Thread 가 기동될 때만 GC 된다 . 하지 만 , Finalizer Thread 는 언제 실행될지도 모르며 또한 한번도 실행되지 않을 수도 있는 특 징이 있다 . 따라서 특별한 경우가 아니면 Object 클래스가 가지는 Finalize Method 를 재정 의 하여 사용하지 말도록 한다 .


6. 결론

IBM JVM GC의 장점에 대해 생각해보자. Throughput 최적화 Collector, Response Time 최적화 Collector의 장점은 안정된 GC 패턴이다. Hotspot JVM이 제공하는 Generation 기법이 훨씬 지능적이고 효과적인 것은 부인할 수 없는 사실이다. 특히 튜닝을 통해 Major GC를 최소화하고 Minor GC를 최적화했다면 GC에 의한 성능 저하 현상을 대부분 피할 수 있다. 하지만 Major GC에 의한 Spike 현상(갑자기 GC Pause Time이 급증하는 현상)은 항상 고질적인 문제로 남아 있다. Minor GC 시에는 안정된 패턴을 보이다가도 Major GC가 발생할 때 수초 ~ 수십 초까지 GC Pause가 발생한다면 성능에 미치는 영향은 치명적일 수 있다.

반면 IBM JVM에서는 상대적으로 Compaction의 발생 빈도가 높지 않기 때문에 전반적으로 안정적인 패턴을 보인다. 이 말은 역설적으로 IBM JVM에서 Compaction이 최적화되고, Sun JVM에서 Major GC가 최적화되면 둘 사이에는 큰 성능 차이가 없을 것이라는 것을 암시하기도 한다.

IBM JVM GC의 단점은 이렇다. Sun JVM이 New/Old Generation의 크기, Survivor Ratio 등의 조정을 통해 세밀한 튜닝이 가능한 반면, IBM JVM의 Throughput 최적화 Collector나 Response Time 최적화 Collector를 사용할 경우에는 튜닝에서의 세밀함이 부족한 편이다. JDK 1.5에서 제공하는 Generational Concurrent Collector를 쓸 경우에는 Sun JVM과 거의 비슷한 설정이 가능하다. 하지만 튜닝 가능한 옵션 수는 비교적 작은 편이다. 하지만 역설적으로 튜닝 옵션이 적다는 것은 더 자동화되어 있고 편리하다는 의미이기도 하다.



위 표는 아무런 GC Option을 사용하지 않은 운영 환경(Throughput 최적화 Collector 사용)에서 2주 동안 3초 이상 수행된 GC 수행이력을 추출한 결과이다. 위 데이터에서 알 수 있듯이 Throughput 최적화 Collector를 사용하는 환경에서는 GC Time이 터무니 없이 긴 Spike 현상이 종종 발생한다는 사실을 알 수 있다. 이는 GC중 발생한 Compaction 작업이 원인이다. 이러한 현상은 실시간으로 계속 서비스가 되어야 하는 환경(이를 테면 MES)에서 장애라고 봐도 무방하다.

GC Time Spike 현상을 해소하기 위해 Generational Concurrent Collector를 사용하도록 Java 옵션을 변경하고 2주 동안 GC추이를 지켜보았다. 그 결과 놀랍게도 모든 GC Time이 1초 이하로 수행되었다. 비록 Scavenger GC로 인해 GC 수행 자체는 더 많이 발생하였으나, GC Time Spike 현상이 없었기 때문에 서비스에 지장을 주는 상황은 발생하지 않았다.

가장 최근에 IBM에서 출시된 Websphere V8의 경우 Default로 Generational Concurrent Collector를 사용하도록 되어있다. 이는 Websphere V8 매뉴얼에서도 확인할 수 있다. 결국 IBM에서는 Hotspot JVM에서 Default로 사용하는 Generational Heap 구조의 효율성을 인정했다는 것으로 해석된다.


부록 1 : JVM Memory Sizing

Heap base에서 Heap limit 까지 가용 크기가 부족하다고 판단되면 최대 Heap top까지 Heap을 확장하며 반대로 Free Heap이 많다 싶으면 축소한다.


확장하는 경우
1) GC 수행해도 Allocation 요구 충족 못하면

2) Free Space 가 전체 하한선 ( - Xmaxf) 보다 낮으면
. 하한선까지 확장 , 단 최대 Heap expansion 크기 ( - Xmaxe) 를 넘진 못함 , 넘으면 - Xmaxe 만큼 확장 (extend)
. 하한선이 1MBytes( - Xmine) 보다 작으면 1MBytes 까지 확장 , 즉 최소 , 최대 extend 값이 있 다 .

3) GC 시간이 임계값 ( - Xmaxt) 을 초과하면


축소하는 경우

1) Free Space가 하한선(-Xmaxf)보다 넘치는 경우 축소량은 하한선까지 한다.

단 -Xms(Heap최소크기)보다 커야 한다. 단, 아래의 경우 축소하지 않는다.



. GC 끝났는데 Object 할당할 크기 확보 못하면
. Free Space 하한선을 100% 로 설정한 경우
. system.GC() 수행할 때 Free Space 의 양이 하한선 밑돌 때
. 최근 3 번 의 GC 를 수행한 후 Heap 이 확장된 적 있을 때

( 확장크기, 축소크기는 32bit 메모리에서 512Bytes단위, 64bit에서는 1024Bytes단위로 늘어남) 정리하면 확장, 축소모두 Free Space가 하한선을 넘느냐 안 넘느냐와 관계가 있다.


부록 2 : IBM JVM의 Heap size 관련 옵션


참조문헌

김한도. Java Performance Fundamental. 서울: 엑셈, 2009
http://ukja.tistory.com/



출처 : (주)엑셈

제공 : DB포탈사이트 DBguide.net