ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA 메모리 트러블 슈팅] 콘솔에서 JVM Heap 메모리 추적하기 : jstat, jmap
    [IT] 공부하는 개발자/JAVA 2019. 9. 10. 20:59

    자바와 힙 메모리 관리

    자바는 기본적으로 자바가상머신, JVM위에서 구동한다. 많은 프로그래밍 언어중에서 자바가 특히 편리한 이유중 하나로 JVM의 GC (Garbage collecter) 를 들 수 있다. 할당되었던 Heap 메모리가 코드에서 더 이상 사용하지 않게 되면, GC는 이 힙 메모리를 자동으로 개간하여 다시 재사용이 가능한 메모리로 돌려보내준다. GC가 힙 메모리를 개간하는 속도보다 사용되는 속도가 더 빠르면, 그 때는 에러가 발생한다.


     

     

     

    메모리 에러가 발생할 때 대처법

    OutOfMemoryError
    // 힙 메모리가 부족할 때 발생하는 메모리 에러

    StackOverflowError
    // 스택 메모리가 부족할 때 발생하는 메모리 에러

     

    1) 일단 코드가 비효율적으로 불필요한 메모리를 소모하고 있지는 않은지 검사한다. (대표적으로 StringBuilder 대신 String 을 사용하는 케이스) 코드가 아니라 다른 곳에 BottleNeck이 숨어있는 경우에도 메모리 에러는 발생할 수 있으므로 전반적인 Bottleneck Checking을 지원하는 APM을 이용하는 편이 좋다. 

     

    2) 코드적으로 문제가 없다면 JVM이 힙/스택에 할당하고 있는 메모리가 너무 적지는 않은지 확인해본다. (톰캣의 경우 기동할 때 -Xms -Xmx 옵션을 이용하여 메모리를 할당하므로, history에서 프로세스 기동 명령어를 확인해 보자.)

     

    3) 두 경우 모두 해당사항이 없다면 트래픽이 서버 Capacity보다 큰 것이므로 스케일 업/아웃 을 고려한다.


     

     

     

    힙 메모리 추적

    보편적으로 APM Tool 을 사용하여 추적하지만, 콘솔에서 간단히 메모리 사용량을 추적할 수도 있다. 

     

    1. top 

    top
    
    // q 로 종료한다

    가장 널리 알려진 방법은 top 커맨드를 이용하는 것이다. 이 경우 실시간으로 자바가 소모하는 메모리 사용량을 확인할 수 있지만, top 커맨드는 소모하는 메모리가 커서 서버에 부하를 주고, 결과를 output으로 저장할 수 없다는 단점이 있으며, 또한 HEAP 메모리 단독으로 사용하는 메모리를 추적하는 것은 불가능하다. (보통 stack 메모리는 1mb를 넘기는 법이 없으므로 큰 차이가 없다고는 해도.) 따라서 육안으로 간단히 확인하는 용도로만 사용하는 것이 바람직하다.

     


     

     

    2. jStat

     

    jstat은 JDK에 내장된 JVM 모니터링 툴이다. OracleJDK, OpenJDK 모두 jstat을 기본으로 내장하고 있어 별도 설치가 요구되지 않는다는 점, 실시간으로 jstat을 사용해도 소모되는 메모리가 6-7mb에 불과해 서버에 부하를 거의 주지 않는다는 점이 장점이다.

     

    # Syntax
    jstat --option $PID $MILLISECOND_INTERVAL $COUNT
    

     

    $PID : JVM 프로세스 아이디

    $MILLISECOND_INTERVAL : 추적하고 싶은 단위 시간을 밀리세컨드로 환산

    $COUNT : 횟수

     

     jstat에 GC Heap을 추적하는 -gc 옵션을 넣어서 heap 메모리를 추적해보겠다. 1000밀리세컨드 동안의 힙 메모리 사용내역을 3번에 걸쳐 출력한다.

     

     

    그러면 위처럼 아주 디테일한 분석 결과가 출력되는데, 각각의 필드가 의미하는 것은 아래와 같다.

     

    필드 DESCRIPTION
    S0C Current survivor space 0 capacity (KB).
    S1C Current survivor space 1 capacity (KB).
    S0U Survivor space 0 utilization (KB).
    S1U Survivor space 1 utilization (KB).
    EC Current eden space capacity (KB).
    EU Eden space utilization (KB).
    OC Current old space capacity (KB).
    OU Old space utilization (KB).
    PC Current permanent space capacity (KB).
    PU Permanent space utilization (KB).
    YGC Number of young generation GC Events.
    YGCT Young generation garbage collection time.
    FGC Number of full GC events.
    FGCT Full garbage collection time.
    GCT Total garbage collection time.

     

    C로 끝나는 S0C, S1C, EC, OC를 합친 것이 Total Heap Size 이고, U로 끝나는 S0U, S1U, EU, OU를 합친 것이 현재 사용중인 Used Heap Size 이다. 가시성을 개선하기 위해, 파이프를 넣어서 커맨드를 아래와 같이 수정한다.

    jstat -gc `pgrep java` 1000 3| tail -n 3 | awk '{split($0,a," "); sum=a[3]+a[4]+a[6]+a[8]; print "used: " sum}'

    현재 jvm의 heap usage는 11054.3 KB이다.  조금 더 응용하면 파이프를 활용하여 사용량 퍼센테이지(%) 등도 구할 수 있다.

    jstat -gc `pgrep java` 1000 3| tail -n 3 | awk '{split($0,a," "); total=a[1]+a[2]+a[5]+a[7]; sum=a[3]+a[4]+a[6]+a[8]; print "used: " sum ", total: " total}'

     

    * jstat이 pid not found 등의 로그 메시지를 출력하며 실행이 되지 않을 때에는, JAVA_HOME 환경변수가 잘 정의되어 있는지 확인하도록 한다. 아파치 톰캣의 경우 ${CATALINA_HOME}/bin의 setenv.sh 나 setclasspath.sh 스크립트 등을 통해 환경변수를 직접 정의하도록  되어 있다.


     

     

    3. jmap

     

    jmap 은 자바 메모리 추적관리를 지원하는 JDK 내장 툴이며, JAVA 9부터 버그 없이 지원된다. jstat처럼 실시간 추적은 지원하지 않지만, 실행하는 그 순간의 heapdump 캡쳐 기능을 지원한다. jstat보다 Report의 가시성이 좋은 것이 장점이다.

    jmap -heap $pid

     

     [ 예상 출력 ] 

    $ jmap -heap 29620
    Attaching to process ID 29620, please wait...
    Debugger attached successfully.
    Client compiler detected.
    JVM version is 1.6.0-rc-b100
    
    using thread-local object allocation.
    Mark Sweep Compact GC
    
    Heap Configuration:
       MinHeapFreeRatio = 40
       MaxHeapFreeRatio = 70
       MaxHeapSize      = 67108864 (64.0MB)
       NewSize          = 2228224 (2.125MB)
       MaxNewSize       = 4294901760 (4095.9375MB)
       OldSize          = 4194304 (4.0MB)
       NewRatio         = 8
       SurvivorRatio    = 8
       PermSize         = 12582912 (12.0MB)
       MaxPermSize      = 67108864 (64.0MB)
    
    Heap Usage:
    New Generation (Eden + 1 Survivor Space):
       capacity = 2031616 (1.9375MB)
       used     = 70984 (0.06769561767578125MB)
       free     = 1960632 (1.8698043823242188MB)
       3.4939673639112905% used
    Eden Space:
       capacity = 1835008 (1.75MB)
       used     = 36152 (0.03447723388671875MB)
       free     = 1798856 (1.7155227661132812MB)
       1.9701276506696428% used
    From Space:
       capacity = 196608 (0.1875MB)
       used     = 34832 (0.0332183837890625MB)
       free     = 161776 (0.1542816162109375MB)
       17.716471354166668% used
    To Space:
       capacity = 196608 (0.1875MB)
       used     = 0 (0.0MB)
       free     = 196608 (0.1875MB)
       0.0% used
    tenured generation:
       capacity = 15966208 (15.2265625MB)
       used     = 9577760 (9.134063720703125MB)
       free     = 6388448 (6.092498779296875MB)
       59.98769400974859% used
    Perm Generation:
       capacity = 12582912 (12.0MB)
       used     = 1469408 (1.401336669921875MB)
       free     = 11113504 (10.598663330078125MB)
       11.677805582682291% used

     

    Can't attach symbolicator to the process ...

     

    그러나 JAVA 8이 설치된 상태에서 jmap을 실행하면 에러가 발생하는데, JAVA 9부터는 해당 에러에 대응하여 결과가 잘 출력되나 JAVA 8 은 공식 지원이  중단된 이후로 업데이트가 되지 않고 있다.

     

     


     

     

     

    Reference

    https://docs.oracle.com/javase/7/docs/technotes/tools/share/jstat.html

     

    jstat - Java Virtual Machine Statistics Monitoring Tool

    jstat - Java Virtual Machine Statistics Monitoring Tool jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ] PARAMETERS generalOption A single general command-line option (-help or -options) outputOptions One or more output options, consis

    docs.oracle.com

    https://docs.oracle.com/javase/7/docs/technotes/tools/share/jmap.html

     

    jmap - Memory Map

    jmap - Memory Map SYNOPSIS jmap [ option ] pid jmap [ option ] executable core jmap [ option ] [server-id@]remote-hostname-or-IP PARAMETERS option Options are mutually exclusive. Option, if used, should follow immediately after the command name. pid proces

    docs.oracle.com

     

    댓글

Copyright in 2020 (And Beyond)