'Memory'에 해당되는 글 2건

  1. 2009.06.25 Out Of Memory Error(OOME)에 대하여
  2. 2009.06.24 JAVA Heap 메모리 모델 (2)
프로그래밍/JAVA2009. 6. 25. 23:07


서버 어플리케이션등 장기간 동작해야하는 프로그램이 어느날 갑자기 메모리 할당을 실패하며 Out Of Memory Exception(OOME)를 발생시키는 경우를 종종 볼수 있다.
 
OOME의 발생 원인은 매우 다양한데 이는 JVM이 사용하는 메모리 공간의 다양성에 기인한다. JVM의 메모리 구분은 다음과 같다.

1. JAVA Heap: 사용자 Object들이 거주하는 공간인다. -Xms<size>와 -Xmx<size> Option에 의해 크기가 결정된다.

** JVM의 Heap 메모리 모델에 대해서는 [http://lyb1495.tistory.com/entry/JAVA-메모리-모델] 참조

2. Permanent Space: Class에 대한 메타 정보를 저장하는 공간이다. PermSize와 MaxPermSize 옵션에 의해 크기가 결정된다.

3. Native Heap: Java Object가 아닌 Native Object들이 거주하는 공간이다. Native Heap의 크기는 JVM Option으로 지정할 수 없으며, OS 차원에서 결정된다.

각 메모리 공간의 용도와 사용 방식이 다르기 때문에 OOME 또한 매우 다양한 상황에서 발생하게 된다. OOME가 발생하는 정확한 원인을 분석하려면 각 메모리 공간의 특성을 이해하고 그에 맞는 해결책을 모색해야 한다.

특히 가장 빈번하게 발생되며 JAVA 프로그래머들의 골치를 아프게하는 JAVA Heap에서의 OOME에 대해 알아보자.

** 비록 JAVA 언어와 JVM이 자동화된 메모리 관리 기능을 제공하지만, 이것이 개발자나 관리자가 메모리 관리에 대해 무신경해도 된다는 것을 의미하지 않는다는 사실을 명심하자. JAVA 에서도 잘못된 메모리 관리는 여전히 많은 문제를 일으키며, Garbage Collection에 의한 성능저하나 OOME에 의한 Applictaion 정지나 System Crash등이 대표적인 예이다.

JAVA Heap에서 OOME가 발생하는 경우에는 다음과 같은 에러 메시지가 출력된다.

 Exception in thread "main": java.lang.OutOfMemoryError: Java heap space

또는

 Exception in thread main: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

전자는 JAVA Heap의 부족으로 Object를 생성하지 못하는 경우에 발생한다. 후자의 메시지는 Java Heap의 최대 크기보다 큰 Array가 요청되는 경우에 발생한다. 가령 Java Heap의 최대 크기가 256M인 상황에서 300M 크기의 Array를 생성하는 경우가 이에 해당한다.

JAVA Heap에서 OOME가 발생하는 이유는 다음과 같다.

  • JAVA Heap이 작은 경우
  • Memory Leak이 발생하는 경우
    • Application Logic에 의한 Memory Leak
    • JDK 버그나 WAS 버그에 의한 Memory Leak
  • finalize 메소드에 의한 Collection 지연

 

1. JAVA Heap 크기와 OOME
JAVA Heap의 최대 크기가 Application의 메모리 요구량에 비해 작게 설정된 경우에 OOME가 발생한다. Memory Leak이 발생하지 않는데도 OOME가 발생한다면 Java Heap의 크기가 부족하다고 판단할 수 있다. -Xmx<size> 옵션을 이용해서 Java Heap의 최대 크기를 키워주어야 한다.

2. Memory Leak과 OOME
Memory Leak이 발생하는 경우에는 Java Heap의 크기와 무관하게 OOME가 발생할 수 있다. 아무리 Java Heap의 크기를 크게 하더라도 결국 Memory Leak에 의해 Collection되지 않는 Garbage 객체들이 메모리를 다 소진하기 때문이다. Memory Leak은 대부분 Application Logic 상의 오류에 의해 발생한다. Object에 대한 참조(Reference) 관계가 복잡한 경우 조그마한 실수로 인해 사용되지 않은 Object를 계속해서 참조하게 된다. 이러한 Object들은 비록 Application에서는 사용되지 않지만 Garbage Collection에 의해 메모리 해제가 이루어지지 않기 때문에 OOME를 유발하게 된다.
JDK Bug나 WAS Bug에 의해서도 Memory Leak이 발생할 수 있다. JDK가 제공하는 라이브러리나 WAS가 제공하는 라이브러리에서 로직 오류로 인한 Memory Leak 가능성이 있기 때문이다. Application Logic에서 Memory Leak이 검출되지 않는 경우에는 JDK나 WAS의 Bug를 의심해볼 필요가 있으며 각 Vendor가 제공하는 Bug Database[SUN JVM: http://bugs.sun.com/]를 통해 검색 가능하다.

** Memory Leak에 대해서는 [http://lyb1495.tistory.com/entry/JAVA-Memory-Leak] 참조

3. finalize 메소드에 의한 Collection 지연과 OOME
특정 Class에 finalize 메소드가 정의되어 있는 경우, 이 Class Type의 Object는 Garbage Collection 발생시 즉각적으로 Collection 되지 않는다. 대신 Finalization Queue에 들어간 후 Finalizer에 의해 정리가 된다. Finalizer는 Object의 finalize 메소드를 실행한 후 메모리 정리 작업을 수행한다. 만일 finalize 메소드를 수행하는데 오랜 시간이 걸린다면 그 만큼 객체가 오랫동안 메모리를 점유하게 된다. 이로 인해 OOME가 발생할 확률이 높아진다. 이런 이유 때문에 finalize 메소드는 되도록 사용하지 말아야 한다.

'프로그래밍 > JAVA' 카테고리의 다른 글

Java 쓰레드  (0) 2009.12.04
스트러츠 properties 한글 편집  (0) 2009.12.03
Java XML Parser JDOM  (2) 2009.09.03
JRE Detection  (0) 2009.08.11
자바 웹 스타트(Java Web Start)  (0) 2009.08.07
JFreeChart with SWT  (0) 2009.07.14
자바 데몬(daemon) 만들기  (2) 2009.07.08
LRU 캐쉬 엔진의 구현  (0) 2009.06.26
JAVA Memory Leak  (2) 2009.06.25
Out Of Memory Error(OOME)에 대하여  (0) 2009.06.25
JAVA Heap 메모리 모델  (2) 2009.06.24
Posted by devop
TAG GC, java, Memory, OOME

댓글을 달아 주세요

프로그래밍/JAVA2009. 6. 24. 14:04
SUN JVM의 Heap은 Old Generation(Tunured space라고도 불림)과 Young Generation으로 구성되어 있다. Young Generation은 다시 Eden과 두개의 Survivor Space(From Space, To Space)로 나누어 볼 수 있다.


Heap 영역의 동작 과정을 간략하게 살펴 본다면 보통 Object는 Eden에서 생성되며 Eden이 Full이 될 때 Live Object는 From Space로 Copy된다. 다시 Eden이 Full이 되면 From Space에서 To Space로 이동된다. 그리고 Eden이 다시 Full일 때, Object가 사용되거나 참조되고 있는 상태라면 Object는 Old Generation으로 이동하게 된다.


Object는 Eden에서 생성이 되고 Eden이 꽉 차게 되면 Memory copy가 발생한다고 하였다. 이러한 작업을 Minor Garbage collection이라 하고 Copy collenction이라고도 한다. Minor Collection시 Garbage Collector는 Eden의 Object를 검사하여 Reference가 없는 것 들은 제거하고 나머지 live Object는 From space가 꽉 찰 때까지 Eden에서 From Space로 이동하게 된다. 이러한 작업은 짧은 생애를 가질 수 밖에 없는 Object는 최대한 짧게 가지고 있겠다는 SUN  JVM의 특징을 그대로 나타내어 준다.

Eden이 다시 차오르면 From Space의 Live Object는 To Space로 이동한다. 그러나 이것은 논리적으로 그렇다는 것이고 실제로 JVM은 각 Minor collection이 발생할 때 마다 Survivor Space의 포인터를 유지하고 있다가 단순히 From Space에서 To Space로 변경해 주는 것이다. Eden이 다시 full이 되면 To Space에 있는 Live Object는 Old Generation으로 카피된다.

Minor Garbage Collection은 매우 빠르고 효율적이다. 소요시간은 Young Generation의 크기에 따라 다르지만 1초 미만이다. 또한 JVM Thread Processing를 멈추는 등의 부작용도 발생하지 않는다.

Young Generation이 모두 꽉차게 되면 Garbage Collector는 가용 메모리를 확보하기 위해 Major Garbage Collection을 수행한다. Major Garbage Collection은 Object들이 live상태로 있는지 여부를 파악하기 위해 모든 Thread의 수행을 잠시 동결시켜 살아있는 Object를 표시하고 Dead Object는 제거하여 Heap을 정리한다. 이러한 작업 때문에 Major Garbage Collection를 mark and sweep collection이라고도 한다. 이 Major Garbage Collection은 Thread를 잠시 멈추게 되고 Mark and Sweep작업을 위해 CPU에 부하를 가하게 되며 이러한 작업은 보통 Minor Garbage Collection에 비해 10배 이상의 시간을 사용하기 때문에 성능에 악영향을 주게 된다.

SUN  JVM은 앞서 언급한 대로 짧은 운명을 가지고 태어난 Object는 짧게, 그리고 장수할 운명을 지닌 Object는 오래도록 유지시키겠다는 의도를 지니고 있다고 하였다. SUN  JVM을 사용할 때는 이러한 의도를 잘 살려 주는 것이 결국 좋은 성능을 내는 것과 밀접한 관계가 있다할 수 있다. 즉 Short Live Object는 Old Generation으로 올라가기 전에 Young Generation에서 제거되게끔 하고 Long Lived Object의 경우 Old Generation에 상주시켜 상대적으로 아주 저렴한 Minor Garbage Collection만으로 Heap의 유지가 가능하게 유도하는 것이 좋다.

이를 위해서는 JVM의 Memory구성이 중요한데 Young Generation은 전체 Heap의 1/2보다 약간 적게 Survivor Space는 Young Generation의 1/8정도의 크기가 적당하다. 이렇게 Heap을 구성하기 위해서는 별도의 Option을 주어야 한다. JVM의 Default의 경우는 Young Generation이 작게 잡혀있기 때문에 Default를 사용하는 것은 권장하지 않는다. Young Generation이 작으면 short Live Object가 Old Generation으로 넘어갈 확률이 커지고 이는 결국 Major Garbage Collection의 발생확률이 높아지기 때문이다.


JAVA 메모리 대한 주요 옵션은 다음과 같다.

-XX:MaxNewSize=<Value> 
Young Generation의 최대 크기를 지정한다. Young Generation의 시작 크기는 NewSize 옵션에 의해 지정된다.

-XX:NewRatio=<Value>
Young Generation과 Old Generation의 비율을 결정한다. 예를 들어 이값이 2이면 Young:Old = 1:2 가 되고, Young Generation의 크기는 전체 Java Heap의 1/3이 된다.

-XX:NewSize=<Value>
Young Generation의 시작 크기를 지정한다. Young Generation의 크기는 NewSize 옵션(시작 크기)과 MaxNewSize 옵션(최대 크기)에 의해 결정된다.

-XX:PretenureSizeThreshold=<value>
일반적으로 Object는 Young Generation에 최초 저장된 후 시간이 흐름에 따라 Tenured Generation으로 Promotion된다. 하지만, Object의 크기가 Young Generation보다 큰 경우 JVM은 Old Generation에 Object를 직접 저장하기도 한다. PretenuredSizeThreshold 옵션을 이용하면 Young Generation을 거치지 않고 직접 Old Generation에 저장하게끔 할 수 있다. 가령 이 옵션의 값이 1048576(1M)이면, 1M 크기 이상의 오브젝트는 Old Generation에 바로 저장된다. 
큰 크기의 오브젝트를 Old Generation에 직접 저장함으로써 불필요한 Minor GC를 줄이고자 하는 목적으로 사용된다.

-Xms<size>
Java Heap의 최초 크기(Start Size)를 지정한다. Java Heap은 -Xms 옵션으로 지정한 크기로 시작하며 최대 -Xmx 옵션으로 지정한 크기만큼 커진다. Sun HotSpt JVM 계열에서는 최초 크기와 최대 크기를 동일하게 부여할 것을 권장한다. 크기의 동적인 변경에 의한 오버 헤드를 최소화하기 위해서이다.

-Xmx<size>
Java Heap의 최대 크기(Maximum Size)를 지정한다. Java Heap은 -Xms 옵션으로 지정한 크기로 시작하며 최대 -Xmx 옵션으로 지정한 크기만큼 커진다. Sun HotSpt JVM 계열에서는 최초 크기와 최대 크기를 동일하게 부여할 것을 권장한다. 크기의 동적인 변경에 의한 오버 헤드를 최소화하기 위해서이다.

** 모든 JAVA 옵션은 [http://wiki.ex-em.com/index.php/JVM_Options] 참조

'프로그래밍 > JAVA' 카테고리의 다른 글

Java 쓰레드  (0) 2009.12.04
스트러츠 properties 한글 편집  (0) 2009.12.03
Java XML Parser JDOM  (2) 2009.09.03
JRE Detection  (0) 2009.08.11
자바 웹 스타트(Java Web Start)  (0) 2009.08.07
JFreeChart with SWT  (0) 2009.07.14
자바 데몬(daemon) 만들기  (2) 2009.07.08
LRU 캐쉬 엔진의 구현  (0) 2009.06.26
JAVA Memory Leak  (2) 2009.06.25
Out Of Memory Error(OOME)에 대하여  (0) 2009.06.25
JAVA Heap 메모리 모델  (2) 2009.06.24
Posted by devop
TAG java, Memory

댓글을 달아 주세요

  1. 김춘석

    궁금증을 간단 명료하게 설명 해주시네요. 정말 감사합니다.

    2010.03.31 12:34 [ ADDR : EDIT/ DEL : REPLY ]
  2. 잘보고가요

    2018.08.29 14:32 신고 [ ADDR : EDIT/ DEL : REPLY ]