태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'GC'에 해당되는 글 5건

  1. 2007.09.13 [Enterprise Java는 거대한 동기화 머신이다] - 재미있는 GC 성능 사례 - Part3
  2. 2007.09.06 Reference 객체는 어떻게 Garbage Collector와 대화하는가? (5)
  3. 2007.09.04 [ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part7 (2)
  4. 2007.08.30 [ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part6 (1)
  5. 2007.08.29 [ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part5 (3)

[Enterprise Java는 거대한 동기화 머신이다] - 재미있는 GC 성능 사례 - Part3

Enterprise Java 2007.09.13 17:51
3. Heap 여유 공간이 충분한데도 OOME(OutOfMemoryException)이 발생한다?

간혹 Heap의 여유 공간이 충분한데도 OutOfMemory Error가 나는 경우가 있다. 이러한 상황을 이해하려면 Java Application이 사용하는 메모리가 여러 영역으로 나뉜다는 사실을 이해해야 한다.

Java Application이 사용하는 메모리 영역은 보통 다음과 같이 분류된다.

- Permanent Space: Class 정보를 저장
- Java Heap: Object 정보를 저장
- Native Heap: JNI, Thread Stack, 기타 Native 정보를 저장

우리가 흔히 접하는 Memory 문제는 대부분 Java Heap에서 발생한다. Java Application이 할당하는 오브젝트들이 Java Heap에 거주하기 때문에 가장 많은 메모리를 필요로 하기 때문이다.

하지만 위의 제목처럼 GC Log 등을 통해 모니터링을 해 본 결과 Java Heap의 여유 공간이 충분한데도 OOEM이 난다면? 다음과 같은 세가지 문제를 의심해보아야 한다.

- Permanent Space가 부족하지 않은가?
- Native Heap이 부족하지 않은가?
- 버그가 아닌가? :)

Permanet Space와 Native Heap에서 발생할 수 있는 메모리 부족 현상에 대해서 알아보자.

- Permanent Space의 부족
GC Log를 통해 Permanent Space가 부족한 지의 여부를 간접적으로 판단할 수 있다.
(GC Log에 대한 자세한 내용은 Google 검색....)

대부분의 Application이 기본 크기만으로 필요한 클래스들을 관리할 수 있다. 하지만 복잡한 Application 들, 특히 많은 수의 Servlet/JSP/EJB/Library 들을 로딩하는 Web Application의 경우 좀 더 큰 크기의 Permanent Space를 요구하기도한다.

-verbose:class 옵션을 사용하면 어떤 클래스들이 로딩되는지 확인할 수 있다.

Permanent Space의 부족 현상으로 판명나는 경우에는 -XX:PermSize-XX:MaxPermSize 옵션을 이용해 Permanent Space의 크기를 키워주어야 한다.

- Thread Stack Size가 큰 경우
우리가 Thread를 사용할 때마다 Native Heap에는 Thread가 사용하는 메모리가 할당된다. 시스템마다 기본적으로 할당되는 크기는 다르다.

Thread Stack의 크기를 256K라고 가정해보자. 4개의 Thread는 1M를 사용하고, 400개의 Thread는 무려 100M를 사용한다. 수많은 Thread를 사용하는 대형 시스템에서는 결코 간과할 수 없는 크기가 된다.

Application 모니터링을 통해 현재 생성한(사용 중인 아닌) 전체 Thread의 개수를 파악해야 한다. 만일 지나치게 많은(수백개 ~ 수천개) Thread가 생성되었다면 지나치게 큰 크기의 Native Heap을 사용하고 필연적으로 OOEM을 유발한다.

Thread 개수를 줄이는 가장 기본적인 방법은 Thread Pool을 사용하는 것이다. Weblogic/Jeus/Websphere와 같은 WAS들이 수백~수천의 동시 사용자를 감당할 수 있는 것은 Thread Pool을 잘 사용하기 때문이다. 대량의 Client와 통신을 수행하는 Server Application을 작성하는 경우에는 반드시 Thread Pool 기법을 사용해서 실제 생성되는 Thread의 개수를 줄여야 한다.

또 다른 방법은 Thread Stack Size를 줄이는 것이다. Thread 개수를 줄일 수 없다면 -Xss128K 정도의 작은 값을 부여해서 메모리 사용량을 줄일 수 있다. 단, Stack 크기가 줄어든 만큼 Stack Overflow 에러가 날 확률이 높아진다.

(PS) Application이 사용하는 Thread 개수를 모니터링하는 가장 좋은 방법은? Thread Dump(kill -3)가 가장 손쉽고 확실한 방법이다. JDK 1.5에서 추가된 Platform MBean(MXBean)과 JConsole 또한 좋은 방법이다. JConsole에 대해서는 아래 URL을 참조한다.

http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html

- File나 Socket을 지나치게 많이 오픈하는 경우
File/Socket을 Resource Limit를 초과하여 오픈하는 경우에도 OOEM이 발생한다. 우선 File이나 Socket을 열고 닫지 않은, 이른바 Resource Leak을 의심해봐야 하며 필요한 경우 다음과 같이 File/Socket의 Resource Limit을 키워야 한다.

ulimit -n 10000

- JNI를 사용하는 Library들...
JDK가 제공하는 Library나 3rd Party가 제공하는 Library들 중에서 JNI를 사용하는 경우 Native Heap을 사용한다. Native Heap은 Java Heap과는 전혀 독립적으로 사용된다. 따라서 Java Heap은 여유가 있음에도 OOEM이 발생하게 된다.

Max Heap(-Xmx) 크기를 500M 정도로 지정했는데 OS 모니터링을 통해 본 Java Process의 메모리 크기가 2G라면? 십중 팔구 Native Heap의 크기가 지나치게 커진 것이다. Thread Stack 크기와 더불어 JNI Heap을 의심해보아야 한다.

Oracle OCI JDBC Driver가 JNI를 사용하는 대표적인 3rd Party Library이다. OCI JDBC Driver는 Thin Driver에 비해 성능 면에서 다소 유리하지만 Native Heap 공간을 많이 사용하는 경향이 있다. 따라서 OCI Driver를 사용하는 경우에는 Process의 메모리 크기를 잘 모니터링해야 한다.

한가지 역설적인 것은 Native Heap을 좀더 크게 사용하기 위해서 Java Heap의 크기를 줄여야 하는 경우가 있다는 것이다. 즉, OutOfMemory 부족 현상이 Native Heap에서 발생하는 경우에는 Java Heap의 크기를 줄이는 것이 대안이 될 수 있다. 32bit 환경에서는 하나의 Java 프로세스가 사용할 수 있는 최대 메모리 공간은 2G로 제한된다. 이 공간을 Java Heap이 다 써버리면 Native Heap이 사용될 수 있는 공간이 그만큼 줄어든다. 그만큼 OOEM이 날 확률이 높아진다.

- 노파심에...
시스템의 물리적 메모리가 2G이다. Java Application의 성능을 극대화하기 위해 Max Heap Size를 1.8G 정도 부여할려고 한다. 바람직한가? 그럴 수도 있지만 생각치 못한 역효과가 있을 수 있다. 앞에서 소개한 몇가지 사례를 기억하자.

Java Heap에 지니치게 많은 메모리를 할당함으로써 Permanet Space나 Native Heap의
부족을 초래하게 되고 이로 인해 OOEM이나 Paging In/Out에 의한 성능 저하 현상이 유발될 수 있기 때문이다.

메모리는 항상 필요한 만큼만...

--------------------------------------------------------------------------------------------------------
다음 글에 계속

신고
tags : GC, Heap, java, OutOfMemory
Trackback 0 : Comment 0

Write a comment


Reference 객체는 어떻게 Garbage Collector와 대화하는가?

Enterprise Java 2007.09.06 16:36
Reference와 Garbage Collector

앞서 블로그(http://blog.naver.com/ukja/120042116328)에서 Java에서 대용량 데이터를 다룰려면 Reference를 사용할 수 있어야 한다고 언급한 바 있다.

간단한 테스트를 통해 Reference를 사용했을 때 Garbage Collection에서 어떤 차이를 유발
하는지 살펴보자.

Reference와 Reachability
JDK 1.2부터 Reference 객체가 제공되며 java.lang.ref 패키지에 필요한 클래스들이 정의되어 있다. Reference 객체는 Application과 Garbage Collector가 서로 대화를 주고받을 수 있는 유일무이한 객체라는데 그 의미가 있다.

일반적으로 Java에서 객체를 참조하는 방식은 다음과 같다.

SomeObject obj = new SomeObject();
...

위에서 obj 변수는 SomeObject 객체를 참조하고 있다. Garbage Collector는 이 객체가 Reachable한지 Unreachable 한 지에 따라 Collection 여부를 결정한다. 아래 두 경우를 보자.

a) Unreachable 객체
for(...) {
SomeObject obj = new SomeObject();
...
}

b) Reachable 객체
SomeObject obj;
for(...) {
obj = new SomeObject();
...
}

a)의 경우 obj는 for() {} 구문이 끝나고 나면 Unreachable 상태가 된다. 따라서 다음 번 GC에서 Collection 대상이 된다. 반면 b)의 경우 obj는 for() {} 구문이 끝나더라도 여전히 Reachable 상태이다. 따라서 Collection 대상에서 제외된다.

JDK 1.2에서 Reference 객체가 소개되기 전까지는 객체의 상태는 Reachable 또는 Unreachable 둘 중의 하나여야만 했다. 하지만 Reference 객체의 등장으로 보다 다양한 상태를 정의할 수 있게 되었다. Java 객체의 상태는 이제 다음과 같이 다섯개로 나누어진다.

Strongly Reachable: Reference 객체를 사용하지 않으면서 Reachable한 상태. 즉 우리가 사용하는 대부분의 Reachable 객체는 Strongly Reachable 상태이다.
Softly Reachable: SoftReference 객체를 통해서 접근되는 상태.
Weakly Reachable: WeakReference 객체를 통해서 접근되는 상태
Phantomly Reachable: PhantomReference 객체를 통해서 접근되는 상태(본 블로그에서는 다루지 않음)

Strongly Reachable 상태는 우리가 평소에 객체를 액세스하는 방식을 말한다. Softly Reachable 상태와 Weakly Reachable 상태는 Reachablility를 더 세분화한다. 한마디로 부드럽게 참조하는 방법과 약하게 참조하는 방법이 추가된 것이다.

Strongly/Softly/Weakly Reachable의 상태 차이는 Garbage Collector가 객체를 어떻게 청소하느냐에 따라 구분된다. Strongly Reachable 상태의 객체는 절대 Collection이 되지 않는다. Garbage가 아니기 때문이다. 반면 Softly Reachable 상태의 객체는 Memory Pressure(주로 Full GC)가 발생할 때 청소가 가능하다. Weekly Reachable 상태의 객체는 Softly Reachable 상태보다 더 약해서 GC가 발생할 때마다 적극적으로 Collection이 된다.

Softly/Weakly Reachable 상태의 객체는 분명히 Garbage가 아니면서도 메모리 요구량이 높아지는 시점에는 마치 Garbage 처럼 처리된다는 속성을 지니고 있다. 이런 의미에서 Reference 객체를 Application과 Garbage Collector가 서로 통신하는 유일한 방식으로 설명하기도 한다.

항상 참조(Reference)는 하고 있지만, 참조 대상이 반드시 메모리에 보관하고 있을 필요가 없는 객체들을 정의하고 싶다면? 바로 SoftReference나 WeakReference를 사용하면 된다. 예를 들어 사용자가 어떤 이미지를 요구할 때마다 메모리에 올려서 내용을 보여주고, 메모리에 올라온 이미지는 Cache에 담아서 재활용한다고 가정해보자. 사용자가 늘어남에 따라 메모리 요구량이 점점 늘어나서 더 이상은 이미지를 Cache에 담을 수 없게 될 것이다. 이런 경우를 해결하려면 LRU 알고리즘과 같은 기법을 써서 복잡한 자료 구조를 구현해야 한다. 하지만 SoftReference를 쓰면 매우 간단하게 해결된다. 즉 메모리에 올라온 이미지 객체를 직접 참조하지 않고 SoftReference를 통해서 참조하는 것이다. 이렇게 되면 Memory Pressure가 발생할 때 자연스럽게 Collection이 이루어지게 된다.

SoftReference를 사용하는 간단한 예제
SoftReference/WeakReference를 사용할 경우에는 Reference가 참조하고 있는 객체가 언제든지 Collection될 수 있다는 전제하에서 코딩을 해야 한다. 아래에 간단한 예제가 있다.(Java 5의 Template 기능을 사용했음...)

- Strong Reference인 경우: 그냥 쓰면 된다
HashMap map = new HashMap();
ImageObject img = new ImageObject(userId + ".gif");
map.put(
userId, img);
...
ImageObject img = map.get(userId);
if(img == null) { // 아직 put 안되었음 }

- Soft Reference인 경우: 참조 대상 객체가 Collection 되었는지 여부를 확인해야 한다.
HashMap> map
= new HashMapSoftReference>();
ImageObject img = new ImageObject(userId + ".gif");
map.put(userId, new SoftReference(img));
...
SoftReference sr = map.get(userId);
if(sr == null) { // 아직 put 안되었음 }
ImageObject img = sr.get();
if(img == null) { // Oops!! Softly Reachable Object가 Collection 되었음...
img = new ImageObject(userId + ".gif");
sr = new SoftReference(img);
map.put(userId, sr);
}
...

Strong Reference/SoftReference/WeakReference의 행동 방식에 대한 간단한 테스트
아래는 Strong Reference/SoftReference/WeakReference가 GC에 어떤 영향을 미치는지 간단하게 테스트한 결과이다

  • Strong Reference/SoftReference/WeakReference를 이용해 대량의 객체를 생성한다
  • 그 후 대량의 메모리를 할당한다(Case1 = 8M, Case2 = 10M)
  • 참조 대상 객체가 Collection되는지를 확인한다.

(소스는 첨부 파일 참조)

1. Object 생성 후 8M를 요구할 때

- Strong Reference
500000 accomplished
Total Application Pause Time = 0.319163
Full GC: Pause Time = 0.000000, Count = 0, Average=0.000000
Minor GC: Pause Time = 0.319163, Count = 4, Average=0.079791

- SoftReference: 아직 참조 대상이 Collection 되지 않았음(Full GC가 생기지 않았음에 유의)
Null = 0, Not Null = 500000 <-- 전부 Not Null
500000 accomplished
Total Application Pause Time = 0.531873
Full GC: Pause Time = 0.000000, Count = 0, Average=0.000000
Minor GC: Pause Time = 0.531873, Count = 6, Average=0.088645

- WeakReference: 일부 참조 대상이 Collection 되었음(Full GC가 없음에도 불구)
Null = 21224, Not Null = 478776 <-- 일부 Null
500000 accomplished
Total Application Pause Time = 0.514273
Full GC: Pause Time = 0.000000, Count = 0, Average=0.000000
Minor GC: Pause Time = 0.514273, Count = 6, Average=0.085712

2. Object 생성 후 10M를 요구할 때

- Strong Reference
500000 accomplished
Total Application Pause Time = 0.545487
Full GC: Pause Time = 0.226339, Count = 1, Average=0.226339
Minor GC: Pause Time = 0.319148, Count = 4, Average=0.079787

- SoftReference: 참조 대상 전체가 Collection 되었음(Full GC가 생겼음)
Null = 500000, Not Null = 0 <-- 전부 Null
500000 accomplished
Total Application Pause Time = 1.521140
Full GC: Pause Time = 1.027627, Count = 2, Average=0.513814
Minor GC: Pause Time = 0.493513, Count = 5, Average=0.098703

- WeakReference: 참조 대상 전체가 Collection 되었음(Full GC가 생겼음)
Null = 500000, Not Null = 0 <-- 전부 Null
500000 accomplished
Total Application Pause Time = 0.980496
Full GC: Pause Time = 0.506724, Count = 1, Average=0.506724
Minor GC: Pause Time = 0.473772, Count = 5, Average=0.094754

위의 결과를 보면 SoftReference와 WeakReference를 사용할 경우 Memory Pressurce가 있을 때 참조 대상 객체가 Collection되는 것을 확인할 수 있다. 따라서 메모리 사용이 좀 더 효율적이 된다.

단 Reference 객체 자체가 메모리를 차지하기 때문에 오버헤드가 있을 수 있다. 하지만 이런 오버헤드는 메모리의 효율적인 사용에 의해 충분히 상쇄된다.

---(참조)----------------------------------------------------------------
java.util.WeakHashMap은 WeakReference를 이용하는 HashMap이다. 만일 중요하지 않은 객체를 메모리에 올리면서 메모리를 효율적으로 사용하기 원한다면 WeakHashMap을 사용해 볼 것을 권장한다.
-------------------------------------------------------------------------

정말 진지한 Application이라면...
비록 SoftReference/WeakReference가 메모리를 효율적으로 사용하는 매우 편리한 방법을 제공하지만 역시 진지한 Application에서 채용할 만큼 효율적인지는 의문이다.

정말 지능적인 Memory Cache를 구현하려면 LRU에 기반한 복잡한 자료구조를 구현할 수밖에 없을 것이다. Garbage Collector의 행동 양식은 Application 입장에서는 예측이 불가능하기 때문에(즉 자주 사용되지 않는 객체를 언제 메모리에서 내릴 것인가...) 신뢰할 만한 기능을 구현하기 어렵다.

반면 좀 덜 진지한 Application이라면 SoftReference나 WeakReference만으로도 쉽게 원하는 효과를 얻을 수 있을 것이다.


신고
tags : GC, java, Reference
Trackback 0 : Comments 5
  1. 맨땅헤딩 2007.09.07 02:22 신고 Modify/Delete Reply

    글 잘보고 있습니다. 근데 뭐 한가지 질문드려도 될가요. 오라클과는 상관없는데...
    저야 하는일이 그래서인지 코딩이나 어플리케이션쪽은 젬병이라서... 제가 돌리는 자바 프로그램하나가 while loop로 비디오의 모든 프레임을 읽어서 image processing을 처리하는게 있는데, 처음 한 5~10분은 제 속도가 나다가 어느순간 무지하게 느려집니다. 처음에는 gc로 어느정도 효과를 보지만 그나마도 시간이 지나면 다시 같은 현상이 나타납니다. 루프안에는 image processing 특성상 array를 좀 많이 사용합니다. 혹시 뭐 집히시는게 있는지...감사합니다.

  2. 맨땅헤딩 2007.09.07 02:26 신고 Modify/Delete Reply

    아.. 그리고 처리된 데이타를 keep track하는 memory-based video database를 가지고 있습니다. video database라고 해서 뭐 거창한거는 아니고, 그냥 extract된 feature values을 Java object으로 관리하는 겁니다. 컬러...기탕 등등. 그렇다고 해당 database크기가 커 봤쟈 1~200메가인데, 제가 사용하는 시스템이 메모리가 1G이거든요...

  3. 욱짜 2007.09.07 09:27 신고 Modify/Delete Reply

    JDK 종류와 버전, OS 정보, VM Option을 포함한 실행 Command, GC Dump(Option), Thread Dump(Option)를 메일로 보내주시면 시간되는 대로 집히는 바가 있는지 볼께요~ 참 그리고 말씀하신 Java Object를 메모리에 담을 때 어떤 자료구조를 사용하나요? java.util에 있는 Collection인지 아니면 자체 제작한 자료구조인지...

  4. 욱짜 2007.09.08 13:52 신고 Modify/Delete Reply

    메일 안주셔서 답글로 남깁니다.(주말이라서 그런가보네요 ^^) 다음 몇가지를 체크해보세요.
    1. Max Heap Size는 충분한가? (단, OS 차원에서 Paging in/out이 생기지 않을 정도로 크게)
    2. New Generation이 지나치게 크지 않은가? Object를 상주시키기 위한 Tenured Space를 크게 하기 위해 -XX:NewRatio=12 정도로 크게 주세요.
    3. 필요하면 Concurrent GC 사용. -XX:+UseConMarkSweepGC 옵션 사용해보세요.

  5. 맨땅헤딩 2007.09.11 23:13 신고 Modify/Delete Reply

    아..제가 독일 컨퍼런스 갔다온다음에 정신이 없어서 이제야 보내요. JDK는 1.4이고 windows xp에서 돕니다. gc dume, thread dump는 제가 잘 모르는거라... 하는일은 each image에 대해서 모든 픽셀 정보를 읽어서 [w][h][3] w:가로픽셀, h:세로픽셀, 3: Red, Green, Blue의 값을 넣는 것인데, 매 루프(이미지)할때 마다 initialize하고 다시쓰거든요. 필요에 따라 데이터를 DB에 저장하구요. 자료구조는 Graph structure를 사용하고 있고, 제가 직접 define한 object을 사용합니다. 그안에 잡다한 feature가 들어가죠. trajectory, color, position등등...주로는 vector를 씁니다.
    한번 말씀하신 옵션을 사용해 보도록 하겠습니다. 감사합니다.

Write a comment


[ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part7

Enterprise Java 2007.09.04 15:32
[Enterprise Java는 거대한 동기화 머신이다]

IBM JVM

- Heap Management & Garbage Collection
IBM JVM의 Heap 메커니즘은 전통적으로 Sun HotSpot JVM과 매우 다른 방식을 사용해왔다. 간단하게 말하면, IBM JVM은 Generation 기법을 사용하지 않았다. 따라서 Young(New)과 Old(Tenured) Generation의 구분이 존재하지 않았다.

"않았다"라는 과거 시제를 사용한 것에 유의하자. IBM Java 5(JDK 1.5)부터는 Generation 알고리즘에 기반한 GC 기법을 제공한다. 이는 Sun HotSpot JVM 계열에서 보편적으로 검증된 방법론을 흡수한 것으로 이해할 수 있다.

Sun HotSpot JVM의 GC 기법을 다룬 블로그(http://blog.naver.com/ukja/120041944714)에서 성능을 보는 두가지 대표적인 관점인 [응답시간(Response Time)]과 [처리량(Throughput)]에 대해 언급한 바 있다. IBM JVM의 GC도 동일한 관점에서 분류된다. JDK 1.4 까지는 다음 세 개의 Garbage Collector가 제공되었다.

  • Throughput 최적화 Collector: -Xgcpolicy:optthruput 옵션으로 지정 (기본값).
  • Response Time 최적화 Collector: -Xgcpolicy:optavgpause 옵션으로 지정
  • SubPool Collector: -Xgcpolicy:subpool 옵션으로 지정. Heap을 여러 개의 Sub pool로 나누어 관리함으로써 성능 향상을 꾀한다. 16개 이상의 멀티 CPU 환경에서만 사용할 것을 권장한다.

JDK 1.5에서는 다음 Garbage Collector가 추가되었다.

  • Generational Concurrent Collector: -Xgcpolicy:gencon 옵션으로 지정. Sun JVM의 Generation 기법과 매우 유사하다.

- Mark and Sweep + Compaction
IBM JVM이 제공하는 기본 Collector는 Young/Old Generation의 구분을 사용하지 않기 때문에 Minor GC(copy), Major GC(mark and sweep)와 같은 구분 또한 존재하지 않는다. 대신 메모리를 정리하는 일련의 과정을 Mark and Sweep + Compaction으로 구분한다. Mark and Sweep 단계는 Sun JVM에서도 이미 언급된 바 있다. 이른바 Stop the World 작업으로 Application Thread를 멈춘 상태에서 Alive Object를 Mark하고(Dead Object를 찾고), 지우는 작업을 한다. IBM JVM은 Mark and Sweep 단계를 매우 "가벼운" 작업으로 간주한다. 비록 이 단계가 Sun JVM의 Major GC와 비슷한 속성을 지니고 있지만 Major GC에 비해서는 단순한 작업으로 구현되어 있다.

Sun JVM의 Major GC와 가장 비슷한 작업은 Compaction 단계에서 발생한다. Mark and Sweep으로 메모리 정리를 한 후에도 오브젝트 할당에 필요한 여유 메모리를 찾지 못하면 Compaction, 즉 압축이 발생한다. Mark and Sweep 작업이 단순히 Dead Object를 정리하는 작업인데 반해 Compaction 작업은 흩어진 프리 메모리를 합치는 작업을 하기 때문에 Mark and Sweep 단계에 비해 많은 시간을 필요로 한다. 다행히 일반적으로 Compaction 작업은 드물게 발생한다.

Mark and Sweep 단계를 거쳤음에도 불구하고 오브젝트 할당에 필요한 메모리를 찾지 못하는 이유는 단편화(Fragmentation)에 있다. IBM JVM의 Mark and Sweep은 Compaction 작업을 하지 않기 때문에 프리 메모리가 여기 저기에 흩어지는 현상이 생기게 된다. 이런 현상을 단편화라고 부른다. 가령 1M의 프리 메모리가 연속되지 않은 10K 청크 100개로 흩어져 있다고 가정해보자. 이 경우 비록 총 1M의 프리 메모리가 존재하지만 20K의 연속된 메모리는 할당될 수없다. 이런 경우에는 Compaction 작업이 추가로 발생하게 된다.

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

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

- Mark and Sweep과 Garbage Collector
Throughput 최적화 Collector는 말 그대로 Mark and Sweep+Compaction 기법을 사용한다. 주기적으로 Mark and Sweep이 발생하고 필요한 경우 Compaction을 통해 메모리 병합을 시도한다.

이러한 일련의 GC 작업은 Application Thread를 완전히 멈춘 상태에서 진행되기 때문에 GC 작업 자체는 가장 최적화된다. 그 만큼 처리량은 높아지지만, Pause Time이 길어지면서 Response Time이 길어지는 단점이 생긴다.

Response Time 최적화 Collector는 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와 같이 동작하며, 그 만큼 Applicaiton Thread의 CPU 자원을 소모한다. 따라서 Throughput 최적화 Collector에 비해 Throughput이 다소 떨어질 수 있다.

---(참조)----------------------------------------------------------------------------------
IBM JVM의 Response Time 최적화 Collector의 GC 수행 기법이 Sun JVM의 Low Pause Collector(CMS Collector)와 매우 유사한 것은 우연이 아니다. 두 Vendor가 최신 Garbage Collection 기법을 서로 차용하고 개발하는 과정에서 자연스럽게 발생하는 공명 현상이다
--------------------------------------------------------------------------------------------

- Global GC와 Scavenger GC
IBM JVM에서는 Minor GC와 Major GC라는 분류법은 존재하지 않는다. 대신 Global GC와 Scavenger GC라는 분류법이 존재한다. Throughput 최적화 Collector(optthruput)와 Response Time 최적화 Collector(optavgpause)가 행하는 GC는 무조건 Global GC이다. 즉 Sun JVM의 관점에서 보면 항상 Major GC를 수행하고 있는 셈이다. 하지만 Compaction이 일어날 때만 진정한 의미에서 Major GC와 같다고 할 수 있다.

IBM JDK 1.5에서 추가된 Generational Concurrent Collector(gencon)에서는 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을 사용한다.

- IBM JVM의 장점
IBM JVM에 제공하는 기본 Collector(optthruput, optavgpause)의 장점은 안정된 GC 패턴이다. Sun 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이 항상 안정적이라는 말은 아니며, Sun JVM에 비해 성능이 뛰어나다는 의미는 더더욱 아니다)

- IBM JVM의 단점
Sun JVM이 New/Old Generation의 크기, Survivor Ratio 등의 조정을 통해 세밀한 튜닝이 가능한 반면, IBM JVM에서 optthruput이나 optavgpause를 사용할 경우에는 튜닝에서의 세밀함이 부족한 편이다.

JDK 1.5에서 제공하는 gencon Collector를 쓸 경우에는 Sun JVM과 거의 비슷한 설정이 가능하다. 하지만 튜닝가능한 옵션 수는 비교적 작은 편이다.

(하지만 역설적으로 튜닝 옵션이 적다는 것은 더 자동화되어 있고 편리하다는 의미이기도 하다)


- IBM JVM의 GC 관련 중요 옵션들

-Xms, -Xmx: Heap의 최소(시작)크기, 최대 크기를 결정한다.

-Xgcpolicy: : optthruput|optavgpause|gencon|subpool. Garbage Collector의 종류(
따라서 Heap 관리의 종류)를 결정한다.

-Xdisableexplicitgc: System.gc()에 의한 GC를 비활성화한다.

-Xgcthreads: Parallel GC 작업을 할 Thread 개수를 지정한다. Default = CPU#-1. 만
일 여러 프로세스가 CPU를 나누어 쓰는 환경이라면 이 값을 낮출 필요가 있다.

-Xloa: LOA(Large Object Area)를 사용할 지의 여부를 결정한다. Default는 활성화상태이다.

-Xloainitial, -Xloamaximum, -Xloaminium: LOA의 초기 크기, 최대크기, 최소 크기를 지정한다. 0 ~ 1 사이의 값을 지정한다.

-Xmaxe, -Xmine: Heap expansion이 증가할 최대/최소 크기를 지정한다.

-Xmaxf, -Xminf: Heap 크기를 조정할 Free Memory의 비율을 결정한다. Default 값은 0.6(60%), 0.3(30%)이다. 즉, Free Memory가 전체 Memory의 60%이상이 되면 Heap Shrinkage가 발생하고, 전체 Memory의 30% 이하이면 Heap Expansion이 발생한다.

-Xmn, -Xmns, -Xmnx: Generational Concrreunt Collector를 사용할 경우 New(Nursery) Generation의 크기(최대/최소를 동일하게), 최소(시작)크기, 최대크기를 지정한다.

-Xmo, -Xmos, -Xmox: Generational Concurrent Collector를 사용할 경우 Old(Tenured) gEneration의 크기(최대/최소를 동일하게), 최소(시작)크기, 최대크기를 지정한다.

-Xpartialcompactgc: Incremental Compaction을 활성화한다. 만일 Compaction에 의한 성능 저하가 발생한다고 판단되면 이 옵션을 사용해본다.

-Xsoftrefthreshold: Soft Reference 객체를 몇 번째 GC Cycle에 해제할지의 여부를 결정한다. Default는 32이다. 즉, 32번째 GC Cycle까지 Soft Reference가 참조하고 있는 객체가 Mark되지 않았다면(즉 Live Object로 판단되지 않았다면) 메모리에서 해제된다. 이 값을 낮춤으로써 프리 메모리가 좀더 빨리 확보되도록 할 수 있다. 하지만 그 만큼 Soft Reference의 효율성은 떨어진다.

---(참조)--------------------------------------------------------------------------------
Reference 객체의 의미는 다음 문서에 잘 설명되어 있다. http://java.sun.com/developer/technicalArticles/ALT/RefObj/

Reference 객체는 Application 개발자가 Garbage Collector의 행동 양식에 영향을 줄 수 있는 유일한 방법이다. 대용량의 메모리를 사용하는 큰 Application 작성자라면 반드시 Reference 객체를 효과적으로 사용할 수 있어야 한다.
------------------------------------------------------------------------------------------
-verbosegc: GC 로그를 남긴다.

-Xverbosegclog: : GC 로그를 특정 파일명으로 남긴다.

간단한 성능 테스트
아래 결과는 세 가지 주요 Collector(optthruput, optavgpause, gencon)의 성능을 비교 테스트한 결과이다.

Case1: Heap Size = 512M, -Xgcpolicy:optthruput
23999893 allocated
Global GC = 22
Scavenger GC = 0
Total GC Time = 7.967951
Avg GC Time = 0.362180

Case2: Heap Size = 512M, -Xgcpolicy:optavgpause
21612553 allocated
Global GC = 23
Scavenger GC = 0
Total GC Time = 0.915822
Avg GC Time = 0.039818

Case3: Heap Size = 512M, -Xgcpolicy:gencon
14321989 allocated
Global GC = 4
Scavenger GC = 122
Total GC Time = 24.730480
Avg GC Time = 0.196274

위의 결과를 해석해보면
  • 처리량은 optthruput 옵션을 사용한 경우에 가장 뛰어나다. 이것은 예상된 결과이다.
  • GC에 의한 Pause Time은 optavgpause인 경우에 가장 낮다. 이것 역시 예상된 결과이다. IBM JVM의 GC가 매우 안정적이고 예상 가능한 방식으로 작동하는 것을 확인할 수 있다.
  • gencon을 사용한 경우 GC Pause Time은 optthruput에 비해 개선되지만 처리량이 지나치게 낮아지는 것을 알 수 있다. 그 이유는 첫째, gencon을 사용할 경우 약간의 옵션 튜닝이 필요할 수 있다는 것과 현재의 Application 패턴이 gencon에는 맞지 않다는 것이다.
J2EE 환경의 복잡한 Application에서는 gencon을 사용한 경우 더 우수한 성능을 나타낸다는 테스트 결과가 있다. 아래 테스트 결과는 복잡한 J2EE Application에서 optthruput과 gencon 옵션을 사용한 경우의 GC 패턴을 보여주고 있다.

파란색 = optthruput, 녹색 = gencon




위의 결과를 보면 처리량면에서도, Pause Time 면에서도 gencon을 사용한 경우가 더 우수한 성능을 보이는 것을 확인할 수 있다.(단 gencon을 사용한 경우 Global GC에 의한 Spike 현상이 나타난 것을 알 수 있다)


아래 결과는 optthruput을 사용하면서 Heap Size를 조정한 경우의 테스트 결과이다.

Case1: Heap Size = 256M, -Xgcpolicy:optthruput
13000597 allocated
Global GC = 32
Scavenger GC = 0
Total GC Time = 15.830381
Avg GC Time = 0.494699

Case2: Heap Size = 1024M, -Xgcpolicy:optthruput
24214091 allocated
Global GC = 23
Scavenger GC = 0
Total GC Time = 9.089497
Avg GC Time = 0.395196

위의 결과를 보면 Heap Size가 큰 경우가 그렇지 않은 경우에 비해 탁월한 성능 개선 효과가 있는 것을 확인할 수 있다. 또한 Sun JVM에서 볼 수 있었던 Heap Size의 크기와 New/Old Generation의 크기에 의한 미묘한 성능 차이 현상이 발생하지 않고 안정적인 패턴을 보이는 것을 알 수 있다. 이러한 안정적인 패턴이 IBM JVM의 가장 큰 장점이다.

---------------------------------------------------------------------------------------------------
다음 글에 계속...

(PS) 애초에 걱정한 것처럼 점점 용두사미로 전락하고 있다... ㅠㅠ







신고
Trackback 0 : Comments 2
  1. moncler 2013.01.04 15:01 Modify/Delete Reply

    관리자의 승인을 기다리고 있는 댓글입니다

  2. moncler españa 2013.01.05 16:32 Modify/Delete Reply

    관리자의 승인을 기다리고 있는 댓글입니다

Write a comment


[ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part6

Enterprise Java 2007.08.30 17:42
Enterprise Java는 거대화 동기화 머신이다.

Sun HotSpot JVM

Heap Management & Garbage Collection
(아래 사실들은 이미 널리 알려진 것들이지만 정리하는 의미에서 간략하게 기술한다)
Sun HotSpot JVM(이후 Sun JVM)은 전통적으로 Generation에 기반한 Heap 관리 방법을 사용한다. Generation, 즉 세대에 기반한다는 것은 오브젝트의 수명에 따라 메모리를 관리한다는 의미다.

Garbage Collection 알고리즘의 개발자들은 통계학적으로 매우 중요한 사실 하나를 발견했다. 그것은 대부분의 오브젝트들이 매우 빨리 죽는다(Short-lived)는 것이다. 즉, 수명이 짧고 덩치가 작은 많은 수의 오브젝트와 수명이 길고 덩치가 큰 적은 수의 오브젝트들이 메모리를 사용한다.

여기서 얻은 통찰력을 기반으로 Generation에 기반한 GC 알고리즘이 등장하게 되었다. 이 알고리즘의 핵심은 어차피 빨리 죽을 오브젝트들은 빨리 메모리에서 밀려나도록, 오래 남을 오브젝트들은 되도록이면 밀려나지 않도록 별도의 공간을 마련해준다는 것이다. Generation 기반의 GC에서는 다음과 같은 방법으로 메모리를 관리한다.
  • 메모리 영역을 New Generation(혹은 Young Generation, Nursery)과 Old Generation(Tenured Generation)으로 나눈다. 즉 어린 오브젝트들이 사는 공간과 나이가 많은 오브젝트들이 사는 공간으로 분리한다.
  • New Generation은 다시 Eden(혹은 Allocate)와 Survivor Space로 나뉜다. Eden은 말 그대로 최초로 태어난(Allocate된) 오브젝트들이 거주하는 공간이다. Eden이 꽉 차면, 죽은 오브젝트들을 정리하고 살아남은 오브젝트들은 Survivor Space(생존자들을 위한 공간)로 옮겨진다. 이 과정에서 흔히 Minor GC으로 불리는 작업이 발생한다.(Survivor Space는 메모리 작업의 효율성을 위해 To Space/From Space라는 두 개의 공간으로 관리된다)
  • Survivor Space가 꽉 차면, 다시 살아남은 오브젝트들은 Tenured Space(종신권을 가진 자들을 위한 공간)으로 옮겨진다.
  • 만일 Tenured Space가 꽉 차면 비상사태가 발생한다. 인구폭발로 인해 더 이상 거주 공간이 없는 상태가 되기 때문이다. 이렇게 되면 JVM은 모든 Thread를 블로킹시키고 Full GC(혹은 Major GC)를 수행한다. 모든 오브젝트들에 대해 생존 여부를 조사해서 죽은 오브젝트들을 실제로 메모리에서 밀어낸다.
  • 이 과정에서 필요하면 메모리 공간을 더 키운다. 확장 가능한 최대 메모리 공간은 -Xmx 파라미터에 의해 지정된다.
  • 이 모든 과정을 수행하고도 메모리를, 즉 오브젝트들의 거주 공간을 확보하지 못하면 OutOfMemory Exception이 발생한다.
(참조)
Full GC를 수행할 때 죽은 오브젝트들을 메모리에서 밀어내는 작업을 흔히 Mark and Sweep이라고 부른다. 즉,살아있는 오브젝트들 "표시(Mark)"하고, Mark되지 않은 오브젝트들을 "쓸어내는(Sweep)" 작업이다.

아래 두 그림은 Sun JVM의 메모리 공간을 표현하고 있다. 그림과 위의 설명을 잘 조합하면 쉽게 이해할 수 있으리라 믿는다.





Sun JVM의 메모리 공간이 매우 다양한 요소들로 이루어져 있기 때문에, 이러한 개별 요소들이 다 세밀한 튜닝의 대상이 된다.

물론 다행인 것은 디폴트 값이나 알고리즘이 어느 정도의 성능을 보장하기 위해 많은 경우 메모리 크기 정도만을 지정하는 것으로 만족할 만한 성능을 얻을 수 있다는 것이다.

Sun JVM이 지원하는 Garbage Collector들
앞서 간략하게 본 것처럼(그리고 널리 알려진 바대로), Sun JVM은 Minor GC(New Generation 정리)와 Full GC(Old Generation 정리)라는 두 종류의 GC를 통해 메모리를 관리한다.

Minor GC 작업과 Full GC 작업이 모두 성능에 지대한 영향을 미치기 때문에, 이 두 개의 GC 작업을 얼마나 효율적으로 할 것인가가 지속적인 관심과 개선의 대상이 되었다. 이러한 작업의 결과로 Sun JVM은 공식적으로 다음과 같은 세 가지 종류의 GC를 제공하게 되었다.
  • Default Serial Garbage Collector: 전통적으로 사용되던 Garbage Collector
  • Throughput Garbage Collector: Throughput 개선에 집중하는 Garbage Collector. Throughput 개선을 위해 New Generation 정리시에 병렬 작업을 수행한다.
  • Low Pause Garbage Collector: Response Time 개선에 집중하는 Garbage Collector. Response Time 개선을 위해 Old Generation 정리 작업을 Applicaiton Thread를 (최대한) 블로킹하지 않는 방식으로 진행한다.


Default Serial Garbage Collector
-XX:+UseSerialGC 옵션에 의해 활성화된다. JDK 1.4에서는 이 Collector가 기본 설정이다. JDK 1.5에서는 약간의 편법(?)이 사용되는데, 만일 JVM이 실행되는 환경이 Server 급이라면 Throughput Garbage Collector가 기본 설정이 된다.

(참조)
Server 급 환경이란? Sun이 설정한 Server 급 환경은 2장 이상의 CPU, 2G 이상의 메모리를 갖춘 환경을 말한다. 이러다 보니 내 노트북이 Server로 분류되는 황당한 상황이... --; 더구나 JDK 1.5에서는 이 기준으로 Server 환경을 정의하고 Server 환경이 되면 -server -XX:UseParalleGC -Xms1024M 같은 옵션을 기본으로 적용한다. 이걸 두고 "인공공학적" 설계라고 부르는데...
사실 좀 장난같다는 느낌이다.

비록 Server급 환경이라도 가용할 수 있는 자원이 많지 않다면 Serial Garbage Collector를 사용하는 것이 일반적으로 바람직하다는 사실에 특히 주의해야 한다. 기계적으로 원칙을 적용해서는 안된다.

Throughput Garbage Collector(혹은 Parallel GC)
-XX:+UseParallelGC
옵션에 의해 지정된다. Throughput Garbage Collector는 New Generation에 의한 Minor GC 작업을 여러 개의 Thread(CPU의 개수만큼)를 이용해 Parallel하게 진행한다.

아래 내용은 Sun JVM에서 -XX:+UseParallelGC 옵션을 지정한 상태에서 생성한 Thread Dump 결과의 일부로, CPU 개수와 동일한 2개의 Garbage Collector Thread가 활성화된 것을 확인할 수 있다.

"GC task thread#0 (ParallelGC)" prio=6 tid=0x00aa67e8 nid=0x8c8 runnable
"GC task thread#1 (ParallelGC)" prio=6 tid=0x00aa7230 nid=0x13a4 runnable

Server 급 머신에서는 이 Collector가 기본 설정이라는 것은 의미하는 바가 크다. 특히 CPU 자원이 풍부한 환경에서는 큰 효과를 얻을 수 있다.

이 Collector를 선택하는데 있어 주의할 점이 하나 있다. 비록 여러 장의 CPU를 갖춘 Server 급 머신이라고 하더라도 다른 프로세스들과 공유해야 환경이라면 이 Collector를 사용하는 것을 바람직하지 않다. 기본적으로 CPU를 충분히 활용하지 못하는 환경이라면 잦은 Context Switching으로 인해 오히려 성능이 저하되기 때문이다.

-XX:ParallelGCThreads 옵션을 이용하면 Parallel Collector Threads의 개수를 조절할 수 있다. 만일 모든 CPU를 충분히 사용하지 못하는 환경이라면 GC Thread의 개수를 줄임으로써 효과적으로 Parallel GC 작업을 할 수 있다.

Low Pause Garbage Collector(혹은 CMS GC)
-XX:+UseConcMarkSweepGC 옵션에 의해 활성화된다. Throughput Garbage Collector와는 달리 Response Time를 최적화하게끔 동작한다. 정확하게 말하면 Response Time을 최소화하기 위해 Full GC 과정을 되도록이면 Thread 블로킹없이 진행한다. Thread의 정지 시간(Pause Time)이 최소화되기 때문에 Low Pause Garbage Collector라는 이름이 붙었다.

Low Pause Garbage Collector는 Full GC 과정에서 오는 Thread Blocking을 최소화하기 위해 다단계의 전략을 사용한다. 아래 내용은 Low Pause Garbage Collector를 사용하는 환경에서 GC Dump를 수행한 내용 중 일부를 발췌한 것으로 Collector의 작업 순서를 알 수 있다.

[GC [1 CMS-initial-mark: 128837K(253952K)] 128919K(262080K), 0.0001592 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs]
[1 CMS-remark: 138222K(253952K)] 145527K(262080K), 0.0232208 secs]
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.086/0.199 secs]
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.006/0.006 secs]

위의 과정은 다음과 같이 요약 설명할 수 있다.
  • initial mark(Pause 발생)-->
  • concurrent mark(Pause 없음. 하지만 Collector와 Application Thread가 CPU를 경쟁)-->
  • remark(Pause 발생)-->
  • concurrent sweep(Pause 없음. 하지만 Collector와 Application Thread가 CPU를 경쟁)
즉, Pause를 최소화하면서(inital mark/remark 과정에서만 Pause 발생) 최대한 Collector Thread와 Application Thread가 동시에 작업을 진행하도록 하는 것이 이 Collector 알고리즘의 핵심이다. Full GC를 Concurrent하게 진행하기 위해 initial mark 작업은 Old Generation이 꽉 차기 전에 시작된다. 디폴트로 Old Generation이 68% 이상 점유되면 Full GC 작업이 시작된다(이 값은 -XX:CMSInitiatingOccupancyFraction 값을로 변경 가능하다. 또한 스스로 메모리 사용량을 모니터링하면서 이 값은 동적으로 변경되기도 한다). 반면 Serial Collector와 Throughput Collector는 Old Generation이 꽉 차야만 Full GC를 시작한다.

Pause Time을 최소화함으로써 Response Time은 최적화되지만, Collector Thread와 Application Thread가 CPU 경쟁을 벌임으로써 전반적으로 Throughput, 즉 일량은 감소하는 부작용이 있다는 사실을 기억해야 한다.

한 가지 재밌는 것은 Low Pause Garbage Collector가 비록 Full GC, 즉 Old Generation에 대한 정리 작업을 최적화하는 기법이지만 New Generation에 대한 Minor GC 작업시에도 여러 개의 Thread를 사용함으로써 성능 개선을 꾀한다는 것이다. 이것은 JDk 1.4에서 실험적으로 고안되었고 JDK 1.5에서는 CPU가 여러 장이라면 기본적으로 Parallel GC를 사용한다. 이것은 -XX:+UseParNewGC 옵션으로 결정된다.
아래 내용은 Low Pause Garbage Collector를 사용하는 환경에서 생성한 Thread Dump의 일부로 New Generation 정리를 위해 여러 개의 Thread가 활성화되어 있는 것을 확인할 수 있다.

"Gang worker#0 (Parallel GC Threads)" prio=10 tid=0x0003bb68 nid=0x1338 runnable
"Gang worker#1 (Parallel GC Threads)" prio=10 tid=0x0003bda8 nid=0x594 runnable

Serial Collector, Throughput Collector(Parallel Collector)와 Low Pause Collector(CMS Collector)는 서로 호환되지 않는다. 가장 이상적인 것은 런타임에 Collector를 선택할 수 있는 것이겠지만 불가능하다. 따라서 시스템의 성능(Client/Server)과 요구 사항(Throughput/Response)에 따라 최적의 Collector를 선택할 수 있도록 해야 한다.

CMS Collector를 사용할 때 한가지 유의할 점은 Heap의 크기이다. CMS Collector는 다른 Collector와는 달리 Full GC를 소극적으로 수행한다. 또한 메모리 Compaction을 적극적으로 하지 않는다. 이런 이유 때문에 메모리 요구량이 많은 경우에는 문제가 생길 수 있다. 따라서 CMS Collector를 사용할 경우에는 Heap 크기를 좀 더 크게 지정해야 한다. 보통 20% 이상의 크기를 추가로 부여할 것을 권고한다.

---(참조)----------------------------------------------------------------------------------------------------------
CMS Collector를 사용할 경우 Permanent Space에 대한 GC 작업을 수행하지 않는다. 따라서 많은 수의 Class를 다루는 Application에서는 Permanent Space에서 OutOfMemory 에러가 날 수 있다. 이 경우에는 다음 두 개의 옵션을 활성해주어야 한다.

-XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled
----------------------------------------------------------------------------------------------------

CMS Collector를 사용할 때 또 한가지 주의할 점은 Mark and Sweep GC가 일어나지 않도록 하는 것이다. CMS Collector는 Old 영역의 사용률을 보고 적당한 시점에 Concurrent한 GC 작업을 시작한다. 만일 이 작업의 시작 시간이 메모리 요구량에 비해서 느리면 Application Thread가 메모리를 필요할 때 할당받지 못하게 되어서 Mark and Sweep GC를 수행하게 된다. 이렇게 되면 Pause Time을 줄이고자 하는 목적의 달성이 어려워진다. Concurrent GC 작업의 시작은 다음 옵션으로 제어된다.

-XX:CMSInitiatingOccupancyFraction=<1~100의 값>

만일 이 값이 60이면 Old 영역의 60%가 사용되는 시점에 Concurrent GC 작업이 시작된다. 만일 이 값이 너무 크면 제 때 GC가 이루어지지 못해 Mark and Sweep GC가 작동할 확률이 높아진다. 반면 이 값이 너무 작으면 Concurrent GC 작업이 너무 일찍 시작해서 Applicaiton Thread의 CPU 점유를 방해한다. 따라서 처리량(Throughput)이 낮아질 수 있다.


중요한 VM 옵션들
Sun JVM의 Heap 및 GC 관리 정책을 결정하는 옵션들은 매우 많다. 너무 많아서 어떤 것을 사용해야 할 지 결정하기가 어려울 정도이다. 어떤 옵션들은 문서화가 거의 되어 있지 않아서 정확한 의미를 이해하기 어려운 것들도 있다.(마치 오라클의 Hidden Parameter 처럼) 아래의 몇 가지 옵션들이 경험적으로 그리고 문서로 정의되고 검증된 것들이다. Heap과 GC의 최적화를 달성하려면 이들 옵션들의 정확한 의미를 이해할 수 있어야 한다.

-Xms: Heap의 초기 크기를 지정한다.
-Xmx: Heap의 최대 크기를 지정한다. 서버용 프로그램에서는 -Xms와 -Xmx를 동일하게 부여함으로써 동적인 메모리 관리의 부담을 덜어주어야 한다.
-Xss: Thread의 최대 Native Stack 크기를 지정한다. 만일 Thread의 개수가 지나치게 많다면 이 크기를 줄여줄 필요가 있다.
-Xoss: Thread의 최대 Java Stack 크기를 지정한다. HotSpot JVM에서는 Thread에 대해 Native Stack만을 사용하기 때문에 이 옵션은 무의미한다.
-XX:NewRatio: New Generation과 Old Generation의 크기 비율이다. 만일 NewRatio 값이 2 이면 New Generation:Old Generation = 1:2 가 된다. 이 값의 경험적 기준치는 8~2 사이의 값을 사용하는 것이다.
-XX:NewSize: New Generation의 크기를 직접 지정한다.
-XX:MaxNewSize: New Generation의 최대 크기를 지정한다. Sun JVM은 New Generation의 크기를 상황에 따라 동적으로 변경(Adaptive)한다. NewSize와 MaxNewSize를 동일하게 하면 크기를 고정할 수 있다. 또는 -XX:-UseAdaptiveSizePolicy 옵션을 지정해도 된다.
-XX:SurvivorRatio: Survivor Space와 Eden Space의 크기 비율을 지정한다. 가령 이 값이 6 이면 (To + From) : Eden = 1:1:6 이 된다. 즉 From Space의 크기가 Eden Space의 1/8이 된다. 경험적으로 추천되는 값은 -XX:NewRatio=2, -XX:SurvivorRatio=6 정도의 값을 사용하는 것이다.
-XX:+UseAdaptiveSizePolicy: Adaptive하게 New Generation의 크기가 Survivor Space의 크기를 변경할 것인지의 여부를 지정한다.
-XX:PermSize: Permanent Space의 최초 크기를 지정한다.
-XX:MaxPermSize: Permanent Space의 최대 크기를 지정한다. 매우 많은 수의 클래스를 로딩하는 경우에는 이 값을 늘려주어야 한다.
-XX:+AggressiveHeap: Heap 사용률을 최대한까지 확장한다. 이 옵션을 활성화하면 Heap은 3850M까지, Thread의 Allocaiton Buffer를 256K까지 확대가능하다. 이 옵션을 지정하면 GC 작업도 Parallel하게 진행된다. 만일 서버급 머신을 하나의 JVM이 사용하는 환경이라면 이 옵션만으로도 적절한 성능을 보장할 수 있다. 개인적으로 이 옵션보다는 개별 옵션을 일일이 지정하는 것이 더 바람직하다고 본다.

-XX:+UseSerialGC: Serial Collector를 활성화한다.
-XX:+UseParallelGC: Throughtput Collector(Parallel Collector)를 활성화한다.
-XX:+UseConcMarkSweepGC: Low Pause Collector(CMS Collector)를 활성화한다.
-XX:+UseParNewGC: CMS를 사용할 경우 Minor GC를 Parallel하게 할 지의 여부를 지정한다. CPU가 복수개일 경우는 기본적으로 이 모드를 사용한다.
-XX:+DisableExplicitGC: System.gc() 호출을 통한 강제 GC 작업을 Disable한다.

-verbose:gc : GC 관련 정보를 콘솔에 출력한다.
-xloggc:: GC 관련 정보를 특정 파일에 출력한다.
-XX:+PrintGCDetails: 상세한 GC 정보를 출력한다.
-XX:+PrintGCTimeStamps: GC 발생 시간 정보를 기록한다.
-XX:+PrintGCApplicationStoppedTime: GC로 인해 Application이 정지된 시간 정보를 기록한다.

위의 옵션 들 중 가장 성능에 많은 영향을 주는 것은 Initail Heap Size, Max Heap Size, NewRatio, SurvivorRatio 그리고 GC의 종류 등이다. 해당 이 옵션 들의 정확한 의미를 꼭 이해하기 바란다. 몇가지 생각해 볼 점을 정리해보자...

New Generation의 크기가 미치는 영향은 어떨까?
New Generation의 크기가 크면 그만큼 Minor GC가 늦게 발생한다. 하지만 GC 시간은 길어진다. 지나치게 큰 New Generation의 문제는 Full GC를 유발할 수 있다는 것이다. 큰 크기의 Eden에 있던 오브젝트들이 한꺼번에 Tenured Space로 옮겨지는 상황에서 Full GC가 유발될 수 있다. New Generation의 크기가 지나치게 작으면 Minor GC가 지나치게 자주 발생하게 된다. 서버급 환경에서의 경험적 수치는 New:Old Generation의 크기를 1:2 ~ 1:8 정도에서 결정하는 것이다.

Survivor Ratio의 크기는 미치는 영향은 어떨까?
Eden에서 살아남은 오브젝트들은 Minor GC 시에 Survivor Space로 옮겨지고, Survivor Space가 꽉 차면 Tenured Space로 옮겨간다. 따라서 Survivor Ratio가 너무 작으면 Full GC가 자주 발생할 확률이 높아진다. 반면 이 값이 너무 크면 Eden Space가 작아서 Minor GC가 지나치게 자주 발생하게 된다. 서버급 환경에서의 경험적 수치는 Survivor Ratio의 값을 6 정도로 지정하는 것이다. 이렇게 되면 To:From:Eden = 1:1:6이 된다.

Max Heap Size가 주는 영향은 어떨까?
Max Heap Size가 주는 성능에 주는 영향은 절대적이다. 상식적으로 클 수록 좋다. Sun JVM의 Generation 관리 기법은 Heap Size가 크다고 해서 큰 성능의 저하가 없게끔 작동한다. 서버급 환경에서의 경험적 수치는 1G ~2G 사이의 값을 사용하고, Initial Size와 Max Size를 동일하게 줌으로써 동적 관리의 부담을 덜 수 있다.
하지만, 항상 큰 Heap이 좋은 것은 아니다. 불필요하게 큰 Heap Size는 Full GC에 지나치게 많은 시간이 걸리는 문제를 유발할 수 있다. Generation 기반 알고리즘의 기본 가정은 수명이 짧고 덩치가 작은 오브젝트는 많고, 수명이 길고 덩치가 큰 오브젝트 수는 적다는 것에 기반한다. 만일 이 가정이 어긋나면 예상치 못한 성능 문제가 생길 수 있다. 이런 경우에는 Heap Size를 줄이거나 New Generation의 크기 비율을 줄이는 방법을 사용할 수 있다. 혹은 Low Pause Collector를 사용하는 것이 바람직하다.(개인적으로는 Low Pause Collector를 사용해볼 것을 권장한다)

Max Heap Size 선정에서 한가지 주의할 점은 OS 레벨에서 Paging In/Out이 생기지 않아야 한다는 점이다. JVM을 구동할 당시에는 메모리 여유가 충분해서 문제가 없다가 JVM이 운영되는 중에 물리적 메모리가 부족하게 되면 Heap의 일부가 Page Out이 될 수 있다. 이런 상황에서는 JVM의 성능이 극도로 저하될 수 있다.

GC Dump
Garbage Collector의 종류에 따라 GC Dump의 포맷이 조금씩 다르다. GC Dump를 정확하게 이해하려면 이 차이를 이해할 필요가 있다.

(아래 예에서는 "-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime" 옵션을 이용해 GC Dump를 수행한다.)

일반적으로 GC 발생시 메모리 변동은 다음과 같은 포맷으로 기록이 된다.

"Generation종류: Before Used Size -> After Used Size (Total Size), 시간"

즉, "DefNew: 18175K->1983K(18176K), 0.0266109 secs"는 다음과 같은 사실을 의미한다.
  • New Generation에 대해 Serial GC를 수행했으며
  • GC 전에는 18M(18175K)를 쓰고 있었고, GC 이후 1.9M(1983K)를 쏘고 있다.
  • New Generation의 전체 크기는 18M(18176K) 이다.
  • GC를 수행하는데는 0.026초 정도 걸렸다.

아래에 간단한 포맷의 예제가 있으니 참조하기 바란다.
1. Serial Collector인 경우
0.272 [GC 0.272: [DefNew: 18175K->1983K(18176K), 0.0266109 secs] 52943K->41607K(260160K), 0.0266978 secs]
...

2. Throughput Collector인 경우
3.303: [GC [PSYoungGen: 15168K->3712K(15744K)] 251928K->244204K(257728K), 0.0386683 secs]
3.341: [Full GC [PSYoungGen: 3712K->0K(15744K)] [PSOldGen: 240492K->95306K(241984K)] 244204K->95306K(257728K) [PSPermGen: 1836K->1836K(16384K)], 0.4015217 secs]

Serial Collector에서 DefNew로 표기되었던 것이 PSYoungGen으로 표기된다. Throughtput Collector의 개념을 이해했다면 그 차이를 정확하게 알 수 있을 것이다.

3. Low Pause Collector인 경우
9.936: [GC [1 CMS-initial-mark: 173808K(253952K)] 174918K(262080K), 0.0011353 secs]
Total time for which application threads were stopped: 0.0029149 seconds
9.937: [CMS-concurrent-mark-start]
9.956: [GC 9.956: [ParNew: 8064K->0K(8128K), 0.0216583 secs] 181872K->176390K(262080K), 0.0217720 secs]
...
10.036: [GC 10.036: [ParNew: 8064K->0K(8128K), 0.0200405 secs] 187044K->181580K(262080K), 0.0201724 secs]
10.060: [CMS-concurrent-mark: 0.057/0.123 secs]
10.060: [CMS-concurrent-preclean-start]
10.061: [CMS-concurrent-preclean: 0.000/0.000 secs]
...
10.189: [GC[YG occupancy: 8064 K (8128 K)]10.190: [Rescan (parallel) , 0.0111151 secs]
10.201: [weak refs processing, 0.0000221 secs] [1 CMS-remark: 191976K(253952K)] 200040K(262080K), 0.0113344 secs]
...
10.663: [CMS-concurrent-sweep: 0.256/0.461 secs]
10.663: [CMS-concurrent-reset-start]
10.670: [CMS-concurrent-reset: 0.007/0.007 secs]
...
Serial Collector에서 DefNew로 표기되었던 것이 ParNew 로 표기되는 것에 주의한다. -XX:+UseParNewGC 옵션이 작동한 결과이다. CMS 로 시작하는 부분은 initial mark->concurrent mark -> remakr -> concurrent sweep 으로 이어지는 Full GC의 활동 상황이 기록된 것이다.

Garbage Collector간의 성능 테스트 예제
아래 표는 Serial Collector/Throughput Collector/Low Pause Collector와 NewRatio의 값을 바꾸어 가면서 성능 비교를 수행한 것이다. JVM 구동시 사용한 다른 옵션들은 다음과 같다
  • -server
  • -Xms=256M
  • -Xmx=256M
  • -XX:+PrintGCDetails
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationStoppedTime
수행한 환경은 다음과 같다.
  • Sun JRE 1.5.06
  • Phsyical Memory 2G
  • CPU = 2 (따라서 JVM 기준으로 서버급 머신)
  • 수행한 프로그램은 첨부한 gc_dump.java 참조
아래에 수행 결과가 있다.

CollectorNewRatio=12
NewRatio=6NewRatio=2
Serial
수행 = 22574172
Pause Time = 14.3
Full GC:
Pause Time = 4.57,
Count = 11,
Average=0.416
Minor GC:
Pause Time = 9.68,
Count = 328, Average=0.029

수행 = 25665194
Pause Time = 14.03
Full GC:
Pause Time = 4.29,
Count = 13,
Average = 0.33
Minor GC:
Pause Time = 9.74,
Count = 195, Average=0.05
수행 = 21854332
Pause Time = 16.1
Full GC:
Pause Time = 11.46,
Count = 29,
Average = 0.39
Minor GC:
Pause Time = 4.68,
Count = 48, Average=0.097
Throughput
(Parallel)
수행 = 17699474
Pause Time = 16.3
Full GC:
Pause Time = 3.46,
Count = 8,
Average=0.43

Minor GC:
Pause Time = 12.8,
Count = 385, Average=0.033
수행 = 19805887
Pause Time = 16.1
Full GC:
Pause Time = 3.67,
Count = 9, Average=0.407

Minor GC:
Pause Time = 12.4,
Count = 233, Average=0.053
수행 = 18852852
Pause Time = 16.5
Full GC:
Pause Time = 5.98,
Count = 13,
Average=0.46

Minor GC:
Pause Time = 10.5,
Count = 103, Average=0.102
Low Pausse
(CMS)
수행 = 17791777
Pause Time = 12.8
Full GC:
Pause Time = 0.00,
Count = 11, Average=0.00

Minor GC:
Pause Time = 12.8,
Count = 555, Average=0.023
수행 = 19268025
Pause Time = 12.3
Full GC:
Pause Time = 0.00,
Count = 13, Average=0.00

Minor GC:
Pause Time = 12.3,
Count = 605, Average=0.02
수행 = 22121742
Pause Time = 13.8
Full GC:
Pause Time = 0.00,
Count = 8,
Average=0.00

Minor GC:
Pause Time = 13.8,
Count = 676, Average=0.02

위의 결과를 보면 Serial Collector이면서 NewRatio가 12인 경우가 Througput 면에서는 가장 우수한 성능을 발휘하는 것을 확인할 수 있다.
반면 Low Pause Collector인 경우에는 NewRatio가 2인 경우에 일량 기준으로도 우수하면서 Pause Time도 가장 낮은 것을 확인할 수 있다.

Low Pause Collector(CMS Collector)는 비록 Throughtput는 낮아질 수 있지만 사용자가 체감하는 응답 시간을 개선시킬 수 있다면 측면에서 적극적으로 활용해볼 만 하다.

Throughput Collector(Parallel Collector)는 가장 좋지 않은 성능을 나타내는데 그 이유는 테스트 장비가 실제로는 서버급이 아니기 때문이다. 많은 수의 CPU를 갖춘 서버급 장비에서는 최소한 Serial Collector보다는 좋은 성능을 내야하는 것이 일반적이다.


Heap Size에 따른 성능 테스트 예제
아래 표는 Heap Size를 256M ~ 1024M로 키우면서 성능을 측정한 결과이다.

Heap Size = 256M
(NewRatio = 2)
Heap Size = 512M
(NewRatio = 2)
Heap Size = 1024M
(NewRatio = 2)
Serial Collector
수행 = 21645002
Pause Time = 16.02
Full GC:
Pause Time = 11.68,
Count = 30,
Average = 0.39
Minor GC:
Pause Time = 4.34,
Count = 45,
Average = 0.096
수행 = 32468630
Pause Time = 13.56
Full GC:
Pause Time = 4.83,
Count 10,
Average = 0.48
Minor GC:
Pause Time = 8.56,
Count = 46,
Average = 0.19
수행 = 42292436
Pause Time = 11.38
Full GC:
Pause Time = 2.0,
Count = 3,
Average = 0.66
Minor GC:
Pause Time = 9.39
Count = 33,
Average = 0.28

위의 패턴을 보면 다음과 같은 사실을 확인할 수 있다.
  • Heap Size가 클 수록 일 처리량(Throughput)은 좋아진다. 그 이유는 GC가 덜 발생하기 때문이다.
  • Heap Size가 클 수록 GC는 적게 발생하지만, GC의 수행 시간은 더 길어진다.
위와 같은 속성을 잘 이해해야 Application에 최적화된 Heap 세팅이 가능하다.

---------------------------------------------------------------------------------
Heap과 GC가 워낙 중요한 이슈이기 때문에 글이 상당히 길어지고 말았다.

다음 글에서는 IBM JVM에 대해서...


신고
tags : Collector, GC, Heap, java
Trackback 0 : Comment 1
  1. 씨알 2008.05.18 00:05 신고 Modify/Delete Reply

    잘 읽고 갑니다. 좋은 정보 감사합니다.

Write a comment


[ Enterprise Java는 거대한 동기화 머신이다 - GC ] Enterprise Java & Oracle 성능 분석의 기초 - Part5

Enterprise Java 2007.08.29 11:22
Enterprise Java는 거대한 동기화 머신이다.

Garbage Collection과 Thread 동기화
Enterprise Java 환경에서 성능에 가장 많은 영향을 미치는 동기화(Synchronization) 문제가 바로 Garbage Collection(GC)이다. WAS 환경을 구축하거나 운영해본 경험이 있는 사람이라면 누구나 GC 문제에 민감할 것이다. 어쩌면 치를 떨지도...?

GC가 발생하는 동안 Application Thread는 어떤 식으로든 작동을 멈추거나 지연이 발생하게 된다. 이런 멈춤(Pause)과 지연이 과도하면 Application의 성능에 치명적인 영향을 미치게 된다. Web Server-->WAS-->DB 로 이루어지는 복잡한 시스템에서는 WAS에서의 GC로 인한 작업 지연이 연쇄 작용을 일으켜 시스템 전체의 성능 저하를 유발할 수 있다.

일반적으로 GC에 의한 성능 저하 문제를 "동기화 문제"로 인식하는 경우가 잘 없기 때문에 "동기화"라는 용어에 고개를 갸우뚱할 지 모르겠다. 필자는 "GC"를 메모리라는 자원을 둘러싼 Thread 동기화의 문제로 보는 것이 가장 정확한 해석이라고 생각한다. Thread가 특정 메모리 영역을 사용하려고 하는 시점에 해당 메모리 영역에 대해 GC 작업이 발생하게 되면 Thread가 블로킹(Blocking)되면서 성능 저하 현상이 발생하는 것이 우리 겪는 GC의 성능 문제이다.

Garbage Collection와 JVM
JVM은 Sun이 정하는 표준이다. 하지만 GC 자체는 표준이 아니다. 다만, Java와 같이 메모리를 핸들링하는 API를 제공하지 않는 어플리케이션에서는 자동화된 메모리 해제 작업이 필요하기 때문에 Garbage Collection 기능이 필수가 된 것 뿐이다.

GC가 표준이 아니기 때문에, GC를 구현하는데 있어서 어떤 알고리즘을 쓸지 또한 전혀 약속된 바가 없다. 각 JVM 벤더들이 자신들 고유의 알고리즘을 통해 GC를 구현한다. 앞으로 몇 차례의 글을 통해 Sun HotSpot JVM과 IBM JVM의 GC의 작동방식과 장단점에 대해 깊이있는 논의를 하게 될 것이다.

Java의 메모리(Non-Heap + Heap) 관리 기법과 GC 알고리즘은 실과 바늘같은 관계이다. GC를 어떻게 할지에 따라 메모리 관리 기법이 결정된다는 것이 정확한 표현일 것이다.

Sun HotSpot JVM과 IBM JVM은 전혀 다른 종류의 GC 기법을 구현해왔다. 최근(특히 Java 5부터)에 와서는 서로의 장점을 흡수해서 상당히 비슷한 개념들을 제공하지만, 여전히 뚜렷한 차이점들을 보인다.

각 JVM이 제공하는 GC의 작동 방식과 장단점을 정확하게 이해해야만 합리적인 성능 개선이 가능하다.

GC의 성능을 보는 두가지 관점
[성능을 개선시킨다]라는 말은 매우 추상적이고 기준이 애매모호하다. 따라서 좀 더 정확한 기준을 정할 필요가 있다. 여기에는 무수히 많은 방법론과 기준이 있겠지만, 보편적으로는 다음과 같은 두 가지의 기준을 사용한다.
  • Throughput 개선: Throughput은 흔히 "처리량"으로 해석된다. 즉 주어진 시간내에 얼마나 많은 일을 하느냐가 곧 Troughput이다.
  • Response Time 개선: Response Time은 "응답시간"으로 해석된다. 즉 특정 요청에 대해 얼마나 빨리 응답을 보내느냐가 Repsonse Time의 기준이다.
Throughput과 Response Time은 상호 배반적인 속성을 지니고 있다. Throughput을 개선시키려면, 즉 주어진 시간내에 최대한 일을 많이 하려면 되도록 통신량을 줄이고 일을 모아서 처리할 수 밖에 없다. 이런 이유로 Throughput을 개선시키기 위해서는 Batch 프로세싱을 사용할 수 밖에 없다.

반면 Response Time을 개선시키려면 작업을 처리하면서 결과가 나오는 즉시 결과를 보내주어야 한다. 이런 이유로 Response Time을 개선시키려면 Looping 프로세싱을 사용할 수 밖에 없다.

(참고) 오라클과 같은 DBMS에서도 동일한 원리가 적용된다. 오라클의 OPTIMIZER_GOAL ALL_ROWS라면 Throughput 기준의 최적화를, FIRST_ROWS라면 Response Time 기준의 최적화를 수행하게 된다. 오라클의 PL/SQL 블록은 항상 수행단위가 블록이기 때문에 예외없이 Throughput 기준의 최적화가 수행된다. 오라클에서도 이 원리를 잘 이해해야만 최적의 쿼리 튜닝이 가능하다.

두 가지 종류의 Garbage Collector
위에서 설명한 이유때문에 어떤 JVM의 Garbage Collector라도 항상 두 가지 종류 이상(Throughput 기준 하나+Response Time 기준 하나)를 제공한다.

Sun HotSpot JVM
IBM JVM
Througput 기준
-XX:+UseParallelGC-Xgcpolicy:optthruput
Response Time 기준
-XX:+UseConnMarkSweepGC
-Xgcpolicy:optavgpause
기타-XX:+UseSerialGC-Xgcpolicy:gencon

Throughput 기준의 Garbage Collector는 공격적으로 GC를 수행한다. 사용자가 체감하는 응답 시간을 다소 희생하더라도 GC 작업 자체를 최대한 효율적이고 집중적으로 수행할 수 있도록 한다. 따라서 GC 작업 시간이 다소 길 수 있고 이로 인해 Thread의 장시간 동기화 및 응답 시간의 지연을 초래하게 된다.

반면 Response Time 기준의 Garbage Collector는 다소 소극적인 GC를 수행한다. GC를 수행하는 과정에서 Thread가 동기화되고 이로 인해 사용자가 대기하는 시간을 최소화하기 위해서 GC 작업 자체에 대한 자원 소모를 줄이면서 오랜 시간에 걸쳐 나누어서 하게 된다. 하지만 그 만큼 비효율적이고 장기적으로는 사용자의 작업을 저해하는 결과를 낳을 수 있다.

각 GC가 제공하는 기능을 정확하게 이해해야만 Application의 요구 사항에 따라 적절한 GC를 선택할 수 있다.

-----------------------------------------------------------------------------------------

다음 글에 계속...




신고
Trackback 0 : Comments 3
  1. 멀더엄마 2007.08.30 17:21 신고 Modify/Delete Reply

    GC 알고리즘도 중요하지만, WAS 인스턴스에 할당하는 메모리 사이즈도 중요한듯.
    메모리 사이즈를 크게주면, Full GC하는 간격이 늘어나지만 그만큼 Full GC 시간이 많이 걸리니 그시간동안 Thread 가 대기하게되는 문제가 있고,
    메모리 사이즈를 작게하면, Full GC 발생 주기가 짧아지는대신 GC 하는 시간이 줄어들어 그만큼 짧은시간동안만 Thread가 대기하게됨.

    WAS 인스턴스 갯수나 request 수 등을 고려해서 적당하게 메모리를 잡으면 될듯. 보통 1~2기가로 잡아주는것같음(???)
    다 ... 아는 얘긴가?

  2. 욱짜 2007.08.30 17:44 신고 Modify/Delete Reply

    Heap Size와 함께 Heap를 구성하는 Young(Eden+Survivor), Tenured Space의 크기도 중요... Part6을 참조

  3. 카이스턴 2007.09.13 11:35 신고 Modify/Delete Reply

    공부중이라 좋은 자료 발견해서 비밀글로 퍼갑니다~

Write a comment

티스토리 툴바