자바가 사용하는 메모리의 종류와 특징

|

http://drkein.tistory.com/95

 

상황인지 미들웨어를 개발하다 보니.. 
성능면에서 등록 가능한 룰의 갯수를 측정해 본적이 있다.
그런데, 약 8만개 정도의 룰을 등록하니 OutOfMemory 에러가 발생.. 
일단 jvm 에서 -Xmx1024로 메모리를 늘려 잡아서 10만개 까지 룰 등록을 하긴 했는데.. 
원인을 알아야 해결을 할 수 있을것 같다.. 

자... 
자바가 사용하는 메모리의 종류엔 뭐가 있을까... 
얼핏 떠오르는게 힙 메모리 밖에 없다..  (근데, Heap 메모리는 뭐지? )

일단 여기저기에서 찾아낸 자바 메모리 관련 정보를 정리해 보자.
일단 자바가 사용하는 메모리의 종류를 알아보고
메모리 관리 방법(Garbage collection 방법)을 파악한 뒤
내 application 에 맞는 최적화 방법을 찾아내는 방법으로 메모리 문제에 접근하는게 순서 인것 같다.

(자바의 garbage collection 관련 링크 : http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html )

1. 자바 VM은 힙을 세개의 영역으로 나누어 사용한다.
  1) New/Young 영역 : 새로 생성된 객체를 저장
  2) Old 영역 : 만들어진지 오래된 객체를 저장
  3) Permanent 영역 : JVM클래스와 메서드 객체를 저장

자바가 사용하는 메모리 구조


여기서 New 영역은 다시  
 a) Eden : 모든 새로 만들어진 객체를 저장
 b) Survivor Space 1, Survivor Space 2 : Old 영역으로 넘어가기 전 객체들이 저장되는 공간
으로 구분된다.

2. Garbage Collector
자바 언어의 중요한 특징중 하나.
전통적인 언어의 경우 메모리를 사용한 뒤에는 일일이 메모리를 수거해 주어야 했다. 
그러나, 자바 언어 에서는 GC기술을 사용하여 개발자로 하여금 메모리 관리에서 자유롭게 했다.

자바의 GC는 New/Young 영역과 Old 영역에 대해서만 GC를 수행한다.  
(Permanent 영역은 code가 올라가는 부분이기 때문에 GC가 필요없다.)

1) Minor GC
New 영역의 GC를 Minor GC라고 부른다. New 영역은 Eden과 Survivor라는 두 영역으로 구분된다.
Eden 영역은 자바 객체가 생성 되자 마자 저장이 되는 곳이다. 
이곳의 객체가 Minor GC가 발생할 때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor1 과 Survivor2로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 살아있는 객체가 Survivor2로 이동되고, Eden 영역과 Survivor1 영역에 남아있는(죽어있는) 객체는 clear된다.
결과적으로, 현재 살아있는 객체들만 Survivor2에 남아있게 된다.
다음번 Minor GC가 발생되면 같은 원리로, Eden과 Survivor2의 살아있는 객체가 Survivor1으로 이동되고, 두 영역은 Clear 된다.
이와 같은 방법으로 반복되면서 메모리를 수거한다. 이런 방식의 GC알고리즘을 Copy & Scavenge라고 한다. 속도가 빠르며 작은 크기의 메모리를 collecting 하는데 효과적이다.
Minor GC 과정중 오래된 객체는 Old 영역으로 복사된다.  
(Kein:얼마나 지나야 '오래된' 객체인 것인지는 명확히 모르겠네요)


2) Full GC
Old 영역의 GC를 Full GC라 한다. Mark & Compact 알고리즘을 이용하는데, 전체 객체들의 reference를 따라가면서 연결이 끊긴 객체를 marking 한다. 이 작업이 끝나면 사용되지 않는 객체가 모두 mark 되고, 이 객체들을 삭제한다. 
실제로는 삭제가 아니라, mark 된 객체로 생기는 부분을 unmark된, 즉 사용중인 객체로 메꾸는 방법이다.

Full GC는 속도가 매우 느리며, Full GC가 일어나는 도중에 순간적으로 java application이 멈춰버리기 때문에 Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 application의 성능과 안정성에 매우 큰 영향을 미치게 된다.

Full GC 동작 순서


3. Garbage Collection이 중요한 이유
Minor GC는 보통 0.5초 이내에 끝나기 때문에 큰 문제가 되지 않는다. 하지만, Full GC의 경우 보통 수 초가 소요되고, GC동안 Application이 멈추기 때문에 문제가 될 수 있다. 5초 동안 서버가 멈춘다면, 멈춰있는 동안 사용자의 request는 쇄도하게 되고, queue에 저장되었다가 요청이 한꺼번에 들어오게되면 여러 장애를 발생할 수 있게 된다.
원할한 서비스를 위해서 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 변수로 작용하게 된다.


4. Garbage Collection 알고리즘들
1) Default Collector
위에 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & Compact를 사용하는 방법이다.

2) Parallel GC
JDK 1.3까지는 하나의 thread 에서만 GC가 수행되었다. JDK 1.4 부터 지원되는 parallel gc 는 minor gc를 동시에 여러개의 thread 를 이용해서 수행하는 방법으로 하나의 thread 에서 gc를 수행하는 것보다 빠른 gc를 수행한다.


하지만, parallel gc가 언제나 유익한 것은 아니다. 1 CPU에서는 오히려 parallel gc 가 느리다. multi thread에 대한 지원이나 계산등을 위해서 4CPU의 256M 정도의 메모리를 보유한 시스템에서 유용하게 사용된다.
parallel gc 는 두가지 옵션을 제공하는데, Low-Pause 방식과 Throughput 방식이다. 
solaris 기준으로 Low-pause 방식은 ?XX:+UseParNewGC 옵션을 사용한다. Old GC를 수행할 때 Application 이 멈추는 현상을 최소화 하는데 역점을 두었다. 
Throughput 방식은 ?XX:+UseParallelGC 옵션을 사용하며, Old 영역을 GC할때는 기본 옵션을 사용하며 Minor GC가 발생했을 때 최대한 빨리 수행되도록 throughput에 역점을 둔 방식이다.

3) Concurrent GC
Full GC를 하는 동안 시간이 길고, Application이 순간적으로 멈추는 현상이 발생하는 단점을 보완하기 위해서, Full GC에 의해 Application이 멈추는 현상을 최소화 하기 위한 방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 하는것이 아니라, 일부는 Application을 수행하고, Application이 멈추었을때 최소한의 작업만을 GC에 할당하는 방법으로 Application이 멈추는 시간을 최소화 한다.
Application이 수행중 일 때 (붉은라인) Full GC를 위한 작업을 수행한다. Application이 멈춘 시간동안에는 일부분의 작업을 수행하기 때문에 기존 Default 방법보다 멈추는 시간이 현저하게 줄어든다.
solaris JVM 에서는 -XX:+UseConcMarkSweepGC 옵션을 사용한다.

4) Incremental GC (Train GC)
Incremental GC 또는 Train GC 라고 불리우는 방법은 JDK 1.3 부터 지원된 방법이다. 의도 자체는 Full GC 동안 Application이 멈추는 시간을 최소화 하는데 목적이 있다.
Minor GC가 일어날 때 마다 Old 영역을 조금씩 GC를 해서, Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.
그림에서 보듯, 왼쪽의 Default GC Full GC가 일어난 후에나 Old 영역이 Clear된다그러나오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부Collect된것을 알 수 있다.
Incremental GC
를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다
.
Incremental GC
는 많은 자원을 소모하고, Minor GC를 자주일으키며Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다오히려 느려지는 경우가 많다.

5. GC 로그 수집 및 분석 방법
이제 적군에 대해 알았으니 나 자신을 파악할 차례다. 
내 Application의 gc 동태를 파악하기 위해 java 실행 옵션에 -verbose:gc 옵션을 주면 gc 로그를 출력할 수 있다. 

garbage collection 로그

로그중 GC 는 Minor GC 이고, Full GC는 Full GC를 나타낸다. 
그 뒤의 숫자는 GC수행 전 heap 메모리 사용량 이다. (New + Old + Perm 영역)
그뒤 -> 이후의 숫자는 GC 수행 후 heap 메모리 사용량을 나타낸다. 
Minor GC 가 수행된 뒤에는 Eden 과 Survivor 영역의 GC가 수행된 것이며, GC이후 heap 사용량은 Old영역의 용량과 유사하다. 괄호 안의 Total Heap Size 는 현재 jvm 이 사용하는 Heap memory의 양이다. 이 크기는 java 실행 옵션의 -Xms -Xmx 옵션으로 설정이 가능한데, 예를 들어 -Xms512 -Xmx1024로 해 놓으면 jvm는 메모리 사용량에 따라서 512~1024m 사이에서 적절하게 메모리 사용량을 늘였다 줄였다 하며 동작한다.
그 다음값은 gc에 소요된 시간이다. 

위의 로그를 보면, Minor GC가 일어날 때 마다 약 20,000Kbytes 정도의 collection이 일어난다. Minor GC는 Eden과 survivor 영역 하나를 gc 하는 것이기 때문에  New 영역을 20,000Kbyte 정도로 생각할 수 있다. 
Full GC 때를 보면 약 44,000Kbytes 에서 1,749Kbytes 로 줄어든 것을 볼 수 있다. Old 영역에 큰 데이터가 많지 않은 경우이다. 
Data를 많이 사용하는 Application의 경우 전체 Heap 이 512M 라 할 때, Full GC 후에도 480M 정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application이 메모리를 많이 사용하는 경우라고 판단할 수 있기 때문에, 전체 Heap 메모리를 늘려주면 효과적이다. 

gc 로그를 분석하여 OutOfMemory가 발생하는 시점을 추적할 수 있다.
위의 설명처럼 Full GC 이후 Heap 메모리가 부족한 경우 전체 heap 을 조정하면 되고
Minor gc 도중 New 또는 survivor의 영역에 부족하여 에러가 발생하는 경우
new 또는 survivor의 영역을 확장하여 문제를 해결할 수 있다.


그럼 이제
내 어플리케이션의 gc 로그를 통해서 어떤 방법이 좋은 것인지 분석해 봐야 겠다

'개발/활용정보 > Java' 카테고리의 다른 글

Creating a Custom Event  (0) 2011.04.19
eclipse 단축키  (0) 2011.04.13
Java 메모리 설정 MaxPermSize  (0) 2011.04.13
Jboss Clustering  (0) 2011.04.13
Java theory and practice: 메모리 누수와 약한 참조  (0) 2011.04.13
And