SSISO Community

시소당

메모리 부족 및 메모리 leak 문제 조사

Out Of Memory(OOM) - Java 힙 또는 기본 메모리가 부족한 경우 응용 프로그램은 "Out of Memory" 오류를 표시합니다.

Memory Leak - Java 힙이나 기본 메모리의 지속적인 사용 증가로 인해 메모리 부족 상황이 발생합니다. 메모리 leak 상황을 디버깅하는 방법은 메모리 부족 상황의 경우와 같습니다.

제 해결
다음 항목을 모두 수행해야 하는 것은 아닙니다.  어떤 경우에는 다음 중 일부만 수행하여도 해결할 수 있습니다.

항목 바로가기:

Java 힙, 원시 메모리 및 프로세스 크기

Java 힙. JVM이 Java 개체를 할당하는 데 사용하는 메모리입니다. Java 힙 메모리의 최대값은 Java 명령줄에서 -Xmx 플래그를 사용하여 지정합니다. 최대 힙 크기가 지정되지 않으면 시스템의 실제 메모리 용량 및 해당 시점에 사용할 수 있는 메모리 용량 등 JVM에서 필요한 요인에 의해 제한 값이 결정됩니다. 항상 최대 Java 힙 값을 지정하는 것이 좋습니다.

Native 메모리. JVM이 내부 작업에 사용하는 메모리입니다. JVM이 사용하는 native 메모리 힙 크기는 생성되는 코드의 양, 생성되는 스레드 수, Java 개체 정보를 보유하기 위해 GC 동안 사용되는 메모리 및 코드 생성, 최적화 등의 작업에 사용되는 임시 공간에 따라 다릅니다.

타사의 native 모듈이 있는 경우에도 native 메모리를 사용할 수 있습니다. 예를 들어, Native JDBC 드라이버는 native 메모리를 할당합니다.

Native 메모리의 최대 크기는 지정된 OS의 가상 프로세스 크기 제한과 -Xmx 플래그를 통해 Java 힙에 적용된 메모리 크기에 따라 제한됩니다. 예를 들어, 응용 프로그램이 총 3GB를 할당할 수 있으며 최대 Java 힙 크기가 1GB이면 가능한 최대 기본 메모리는 약 2GB입니다.

프로세스 크기 - 프로세스 크기는 Java 힙, native 메모리 및 로드된 실행 파일과 라이브러리에서 차지하는 메모리를 합한 것입니다. 32비트 운영 체제에서 프로세스의 가상 주소 공간은 최대 4GB까지 가능합니다. 이 4GB 중에서 일부(보통 1.2GB)는 OS 커널이 자체 사용을 위해 예약한 공간입니다. 나머지는 응용 프로그램에 사용할 수 있습니다.

Windows. 기본적으로 2GB는 응용 프로그램에 사용할 수 있고 2GB는 커널용으로 확보됩니다. 그러나 일부 버전의 Windows에는 응용 프로그램에 3GB를 할당하도록 비율을 변경할 수 있는 /3GB 스위치가 제공됩니다. /3GB 스위치에 대한 자세한 내용은 다음 사이트에서 찾을 수 있습니다.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ddtools/hh/ddtools/bootini_1fcj.asp

RH Linux AS 2.1. 응용 프로그램에서 3GB를 사용할 수 있습니다.

다른 운영 체제의 경우 자신의 구성에 해당하는 OS 설명서를 참조하십시오.

페이지 맨 위

프로세스 주소 공간과 실제 메모리 간 차이점

각 프로세스는 자체의 주소 공간을 확보합니다. 32비트 운영 체제에서 이 주소 공간은 0-4GB 이내입니다. 이 크기는 시스템에서 사용할 수 있는 RAM 또는 스왑 공간과는 무관합니다. 시스템에서 사용할 수 있는 실제 총 메모리 크기는 RAM 및 스왑 공간 크기를 합한 것과 같습니다. 실행 중인 모든 프로세스는 이 실제 메모리를 공유합니다.

프로세스 내의 메모리 주소는 가상 주소입니다. 커널은 이 가상 주소를 실제 주소에 매핑합니다. 실제 주소는 실제 메모리상의 위치를 가리킵니다. 항상, 시스템에서 실행 중인 프로세스에 사용되는 모든 가상 메모리의 합계가 해당 시스템에서 사용할 수 있는 총 실제 메모리 크기를 초과할 수 없습니다.

페이지 맨 위

OOM 문제가 발생하는 이유 및 이러한 상황에서 JVM이 수행하는 작업

Java 힙의 메모리 부족
JVM은 Java 힙에서 더 많은 Java 개체를 할당하기 위한 메모리를 확보할 수 없는 경우 Java OOM(메모리 부족) 오류를 던집니다. JVM은 Java 힙이 활성화된 개체로 가득 차서 Java 힙을 더 이상 확장할 수 없는 경우 Java 개체를 더 이상 만들 수 없습니다.

이 경우 JVM은 java.lang.OutOfMemoryError를 던진 후에 응용 프로그램에서 수행할 작업을 결정할 수 있도록 합니다. 예를 들어, 응용 프로그램은 이 오류를 처리하고 안전한 방식으로 종료할지 또는 오류를 무시하고 계속 실행할지를 결정할 수 있습니다. 응용 프로그램이 이 오류를 처리하지 않으면 이 오류를 던진 스레드가 종료됩니다. (Java 스레드 덤프를 가져올 경우에는 이 스레드가 나타나지 않습니다.)

weblogic 서버에서는 이 오류가 Execute 스레드에 의해 발생한 경우 오류가 처리된 후 기록됩니다. 이 오류가 계속 발생하면 코어 상태 모니터(Core Health Monitor) 스레드에 의해 weblogic 서버가 종료됩니다.

Native 힙의 메모리 부족
JVM은 더 이상의 native 메모리를 확보할 수 없는 경우 native 메모리 부족(OOM) 오류를 던집니다. 이러한 오류는 일반적으로 프로세스가 해당 OS의 프로세스 제한에 도달하거나 시스템에서 RAM 및 스왑 공간이 부족한 경우에 발생합니다.

이러한 오류가 발생하면 JVM은 Native OOM 상황을 처리하고, 기본 메모리가 부족하거나 메모리를 확보할 수 없다는 메시지를 기록한 후 종료됩니다. JVM 또는 로드된 기타 모듈(libc 또는 3rd party 모듈)이 이 Native OOM 상황을 처리하지 못할 경우 OS는 JVM을 종료시키는 sigabort 시그널을 JVM으로 보냅니다. 일반적으로 JVM은 sigabort 신호를 받으면 코어 파일을 생성합니다.

페이지 맨 위

제 디버그 단계

Java OOM인지 또는 Native OOM인지 확인

  • stdout/stderr 메시지에 java.lang.OutOfMemoryError로 나타나는 오류는 Java OOM입니다.
  • stdout/stderr 메시지에 메모리 확보 실패로 나타나는 오류는 Native OOM입니다.

위의 메시지는 weblogic.log 같은 응용 프로그램 특정 로그 파일이 아닌 stdout 또는 stderr로 보내진다는 점에 유의하십시오.

페이지 맨 위

Java OOM의 경우:

  1. 자세한 gc 출력을 수집하고 분석합니다.
    1. java 명령줄에 '-verbosegc' 플래그를 추가합니다. 이렇게 하면 GC 작업 정보가 stdout/stderr로 출력됩니다. stdout/stderr을 파일로 라다이렉션합니다. 문제가 재현될 때까지 응용 프로그램을 실행합니다.
    2. JVM은 Java OOM을 던지기 전에 다음을 수행합니다.

      Full GC 실행:
      Full GC를 실행하면 레퍼런스가 끊어진 개체나 Phantom, Week, Soft 레퍼런스 개체가 모두 제거되고 해당 공간이 확보됩니다. 다른 수준의 개체 연결에 대한 자세한 내용은 다음 사이트를 참조하십시오.
      http://java.sun.com/developer/technicalArticles/ALT/RefObj

      OOM 메시지가 발생하기 전에 Full GC가 실행되었는지 확인할 수 있습니다. Full GC가 실행되면 다음과 같은 메시지가 출력됩니다. (형식은 JVM에 따라 다릅니다. 형식을 이해하려면 JVM 도움말 메시지를 확인하십시오.)

      [memory ] 7.160: GC 131072K->130052K (131072K) in 1057.359 ms

      위에 표시된 출력의 형식은 다음과 같습니다. (참고: 이 패턴 전체에서 동일한 형식이 사용됩니다.)

      [memory ] <start>: GC <before>K-><after>K (<heap>K), <total> ms
      [memory ] <start> - start time of collection (seconds since jvm start)
      [memory ] <before> - memory used by objects before collection (KB)
      [memory ] <after> - memory used by objects after collection (KB)
      [memory ] <heap> - size of heap after collection (KB)
      [memory ] <total> - total time of collection (milliseconds)

      그러나 자세한 메시지를 통해서도 Soft/Weak/Phantom 레퍼런스 개체가 제거되었는지는 확인할 수 없습니다. OOM이 발생했을 때 이러한 개체가 여전히 존재하는 것으로 의심되면 JVM 공급업체에 문의하십시오.

      가비지 콜렉션 알고리즘이 generational 알고리즘(Jrockit의 경우 gencopy 또는 gencon, 기타 JDK의 경우 디폴트 알고리즘)인 경우에도 다음과 같은 자세한 출력 결과가 표시됩니다.
      [memory ] 2.414: Nursery GC 31000K->20760K (75776K), 0.469 ms

      위의 출력은 활성화된 개체를 Nursery(또는 Young 공간)에서 Old 공간으로 승격시키는 Nursery GC(또는 Young GC) 주기를 나타냅니다. 이 주기는 현재 우리가 진행하는 이 과정에는 중요하지 않습니다. generational 알고리즘에 대한 자세한 내용은 JVM 설명서를 참조하십시오.

      만약 Java OOM 이전에 GC 과정이 나타나지 않으면 JVM 버그입니다.

      Full Compaction:
      JVM이 compaction 작업을 올바로 수행하는지, 메모리가 조각화(fragment)되어 큰 개체를 할당할 수 없거나 Java OOM 오류가 발생할 수 있는 상황이 아닌지 확인하십시오.

      Java 개체는 연속적인 메모리가 필요합니다. 사용 가능한 메모리가 조각화되면 사용 가능한 연속적인 공간이 없으므로 JVM이 큰 개체를 할당할 수 없습니다. 이 경우 JVM은 더 많은 연속적인 사용 가능한 메모리를 확보하여 큰 개체를 수용할 수 있도록 Full Compaction을 수행해야 합니다.

    Compaction 작업에는 Java 힙 메모리의 한 위치에서 다른 위치로 개체(데이터)를 이동하고 새 위치를 가리키도록 해당 개체에 대한 참조를 업데이트하는 과정이 포함됩니다. JVM은 필요하지 않을 경우 모든 개체를 compact 하지 않습니다. 따라서 GC 주기의 일시 중단 시간이 줄어듭니다.

    자세한 gc 메시지를 분석하면 Java OOM이 조각화로 인해 발생한 것인지 확인할 수 있습니다. 사용 가능한 Java 힙이 있는지 여부에 관계없이 다음과 유사한 출력이 표시되고 OOM이 발생하면 조각화가 그 원인입니다.

    [memory ] 8.162: GC 73043K->72989K (131072K) in 12.938 ms
    [memory ] 8.172: GC 72989K->72905K (131072K) in 12.000 ms
    [memory ] 8.182: GC 72905K->72580K (131072K) in 13.509 ms
    java.lang.OutOfMemoryError

    위의 경우에서 지정된 최대 힙이 128MB이고 실제 메모리 사용량이 겨우 72580K일 때 JVM에서 OOM이 발생했음을 알 수 있습니다. 힙 사용량은 겨우 55%입니다. 따라서 이 경우에는 사용 가능한 힙이 45%나 있지만 조각화로 인해 OOM이 throw됩니다. 이 문제는 JVM의 버그 또는 한계입니다. 이러한 문제가 발생하면 JVM 공급업체에 문의해야 합니다.

  2. JVM이 제대로 작동되는 경우(위의 단계에서 언급된 모든 상황) Java OOM은 응용 프로그램 문제일 수 있습니다. 응용 프로그램이 Java 메모리 일부를 지속적으로 leak시키는 경우 이러한 문제가 발생할 수 있습니다. 또는 응용 프로그램이 너무 많은 개체를 사용하여 Java 힙 메모리가 더 많이 필요한 경우입니다. 응용 프로그램에서 다음 사항을 확인할 수 있습니다.
    • 응용 프로그램의 캐싱 - 만일 응용 프로그램이 메모리에 Java 개체를 캐시하고 있다면 이 캐시가 지속적으로 커지지 않는지 확인해야 합니다. 캐시에 저장되는 개체 수는 제한되어 있습니다. 이 제한을 줄이면서 Java 힙 사용량이 줄어드는지 확인해 볼 수 있습니다.

    Java SoftReference는 JVM에서 Java 힙이 부족한 경우 Soft 레퍼런스 개체의 제거를 보장하게 때문에 데이터 캐싱에 사용될 수도 있습니다. (역주: SoftReference에 할당된 개체는 OOM이 발생하기 전에 반드시 해제되어 힙 메모리 부족 현상을 막을 수 있음)

    • 수명이 긴 개체 - 응용 프로그램에 수명이 긴 개체가 있는 경우 가능하면 해당 개체의 수명을 줄여보십시오. 예를 들어, HTTP 세션 시간 초과를 조정하면 유휴 세션 개체를 더 빠르게 확보하는 데 도움이 됩니다.
    • 메모리 Leak - 메모리 leak의 한 예로 응용 프로그램 서버의 데이터베이스 커넥션 풀을 사용하는 경우를 들 수 있습니다. 커넥션 풀을 사용할 때는 finally 블록에서 JDBC 문과 ResultSet 개체를 명시적으로 닫아야 합니다. 그 이유는 풀의 Connection 개체에서 close()를 호출하면 해당 연결을 재사용하기 위해 풀로 다시 반환하기만 할 뿐 실제로는 해당 Connection 및 관련된 Statement/ResultSet 개체가 닫히지 않기 때문입니다.
    • Java 힙 늘리기 - Java 힙을 늘리면서 문제가 해결되는지 확인해 볼 수 있습니다.

  1. 위의 모든 제안을 응용 프로그램에 적용할 수 없는 경우, Jprobe 또는 OptimizeIt 같은 JVMPI(JVM Profiler Interface) 기반 프로파일러를 사용하여 Java 힙을 점유하고 있는 개체를 찾아내야 합니다. 또한 이 프로파일러는 Java 코드에서 이러한 개체가 만들어지는 위치에 대한 자세한 정보를 제공합니다. 이 문서에서는 각 프로파일러에 대해 상세히 다루고 있지 않습니다. 이 프로파일러를 이용하여 응용 프로그램을 설정 및 시작하는 방법을 이해하려면 프로파일러 설명서를 참조하십시오. 일반적으로 JVMPI 기반 프로파일러는 큰 오버헤드를 발생하므로 응용 프로그램의 성능을 크게 저하시킵니다. 따라서 운영 환경에서는 이러한 프로파일러를 사용하지 않는 것이 좋습니다.
    http://www.borland.com/optimizeit
    http://www.quest.com/jprobe

페이지 맨 위

Native OOM 문제의 경우:

  1. 다음 정보를 수집하여 참조하십시오.
    1. Java 힙 사용량을 모니터링하기 위한  -verbosegc 출력. 이 정보는 이 응용 프로그램에 대한 Java 메모리 요구 사항을 파악하는 데 도움이 됩니다.
    지정된 최대 힙 크기(Java 명령줄에서 -Xmx 플래그를 사용하여 지정)는 응용 프로그램의 실제 Java 힙 사용량과는 관계없이 JVM 시작 시 할당되며 이렇게 할당된 메모리는 다른 용도로 사용할 수 없습니다.

    Jrockit의 경우 -verbosegc 대신 GC 정보와 함께 코드 생성 정보를 제공하는 -verbose를 사용하십시오.
    1. JVM이 native 메모리가 부족해질 때까지 응용 프로그램이 시작된 이후부터 주기적으로 프로세스 가상 메모리 크기를 기록합니다. 이 작업은 해당 프로세스가 실제로 OS의 크기 제한에 도달했는지 파악하는 데 도움이 됩니다.
    Windows에서는 다음 절차에 따라 가상 프로세스 크기를 모니터링하십시오.
      1. 시작 -> 실행... 대화 상자에서 "perfmon"을 입력한 후 확인을 누릅니다.
      2. "Performance" 팝업 창에서 그래프 위의 '+' 단추를 누릅니다.
      3. 나타나는 대화 상자에서 다음 옵션을 선택합니다.
        • Performance object: Process(디폴트값인 Processor가 아님)
        • Select counter from list: Virtual Bytes
        • Select instances from list: JVM(java) 인스턴스를 선택합니다.
        • "Add"를 누른 후 "Close"를 누릅니다.

Unix 또는 Linux에서는 지정된 PID에 대해 ps -p <PID> -o vsz 명령을 실행하여 가상 메모리 크기를 확인할 수 있습니다.

Linux의 경우 단일 JVM 인스턴스 내의 각 Java 스레드가 별도의 프로세스로 표시됩니다. 이 때 루트 Java 프로세스의 PID만 구하면 됩니다. 루트 Java 프로세스는 ps 명령에 --forest 옵션을 사용하여 찾을 수 있습니다. 예를 들어, ps -lU <user> --forest를 실행하면 지정된 사용자가 시작한 모든 프로세스에 대한 ASCII 트리 구조가 표시됩니다. 트리 구조에서 루트 Java를 찾을 수 있습니다.

  1. 시스템의 메모리 가용성
    시스템에 충분한 RAM 및 스왑 공간이 없으면 OS는 이 프로세스에 더 많은 메모리를 할당할 수 없으므로 메모리 부족 현상이 발생할 수 있습니다. 디스크의 RAM과 스왑 공간을 합할 경우 해당 시스템에서 실행 중인 모든 프로세스를 충분히 처리할 수 있는지 확인하십시오.
  1. Java 힙 조정
    Java 힙 사용량이 최대 힙 크기를 넘지 않을 경우 최대 Java 힙을 줄이면 JVM에 더 많은 native 메모리가 할당됩니다. 이 방법은 완벽한 해결책은 아니지만 시도해 볼만한 가치가 있습니다. OS는 프로세스 크기를 제한하므로 Java 힙과 기본 힙 간의 균형을 깨뜨릴 필요가 있습니다.
  1. JVM의 Native 메모리 사용량
    모든 클래스가 로드되고 메쏘드가 호출된 후(코드 생성이 완료된 후) JVM의 기본 메모리 사용량이 윈상복귀됩니다. 이 과정은 대부분의 응용 프로그램에서 처음 몇 시간 이내에 발생합니다. 그 이후에 JVM은 런타임 클래스 로드, 코드 생성, 최적화 등의 작업에 필요한 소량의 native 메모리만 사용합니다.

    문제의 범위를 좁히기 위해 런타임 최적화 옵션들을 비활성화하고 차이가 있는지 확인해 보십시오.

    • Jrockit에서는 런타임 최적화를 비활성화하기 위해 -Xnoopt 플래그를 사용할 수 있습니다.
    • SUN 핫스폿 JVM에서는 -Xint 플래그를 지정하면 JVM은 강제로 interprete 모드(코드 생성 없음)로 실행됩니다.

    실행 과정 중에 native 메모리 사용량이 지속적으로 증가하면 원시 코드에 메모리 leak 때문일 수 있습니다.

  2. 응용 프로그램의 타사 Native 모듈 또는 JNI 코드
    데이터베이스 드라이버 같은 타사 native 모듈을 사용하고 있는지 확인하십시오. 이러한 native 모듈도 native 메모리를 할당할 수 있으며 이러한 모듈에서 leak이 발생할 수 있습니다. 문제의 범위를 좁히기 위해 이러한 타사 모듈을 사용하지 말고 문제를 재현해 보십시오. 예를 들어, native 데이터베이스 드라이버 대신 pure Java 드라이버를 사용할 수 있습니다.

    응용 프로그램이 JNI 코드 일부를 사용하는지 확인하십시오. 이러한 경우에도 기본 메모리 leak이 발생할 수 있으므로 가능하면 JNI 코드를 사용하지 말고 응용 프로그램을 실행해 보십시오.

  3. 위의 단계를 수행한 후에도 native 메모리 Leak의 원인을 찾을 수 없으면, JVM 공급업체에 문의하여 native 메모리 할당 호출을 추적하고 leak에 대해 보다 자세한 정보를 제공할 수 있는 특별한 빌드를 구해야 합니다.

페이지 맨 위

Jrockit 특별 기능
Jrockit 81SP1 이상 버전은 JRA 기록(Java Runtime Analyzer)을 지원합니다. 이 기능은 JVM 런타임에 응용 프로그램, 실행한 GC의 수, (소프트/약함/가상) 참조 수, 핫 메쏘드 등에 대한 정보를 수집할 경우에 유용합니다. JVM에 성능 문제나 중단 문제가 있는 경우 몇 분 동안 데이터를 기록한 후 분석하는 것이 유용합니다. 이 사항에 대한 자세한 내용은 다음 Jrockit 설명서를 참조하십시오.
http://e-docs.bea.com/wljrockit/docs142/userguide/jra.html

참조 자료

페이지 맨 위

추가 도움말이 필요하십니까?

패턴대로 작업했지만 추가 도움말이 필요한 경우 다음과 같이 할 수 있습니다.

  1. http://support.bea.com의 AskBEA에서 "out of memory"로 문제를 조회하여 게시된 다른 해결 방법을 찾아봅니다.
  2. http://newsgroups.bea.com 사이트에서 BEA 뉴스그룹에 보다 자세한 내용을 질문합니다.

이렇게 해도 문제점이 해결되지 않으며 유지보수 계약이 되어 있다면 http://support.bea.com/에 로그인하여 Support Case를 열 수 있습니다.

9585 view

4.0 stars