사용자 도구

사이트 도구


java:memory

Java Memory Analysis

기본 Heap 메모리 설정

  • 장비 메모리에서 OS와 다른 애플리케이션이 사용하는 것을 제외하고 설정할 수 있다.(보통 2GB정도 남겨두고 나머지를 설정하면 될듯)
  • 하지만 Lucene의 경우 off heap 메모리를 사용하기 때문에 ElasticSearch는 장비 메모리의 50%만 heap으로 설정함. off heap 사용시에 관련 사항 주의.
  • 운영체제 메모리가 아무리 많아도 heap은 32GB를 넘기지는 않게 한다. 31GB 정도 추천
  • Linux 운영체제 swap 일어나는 것을 방지하기 위해 swappiness=1 로 조정한다. Linux Performance

Default 값

  • 운영체제에 따라 Java 버전에 따라 또한 현재 실행하고 있는 시스템의 메모리 상황에 따라 기본 메모리 설정이 다를 수 있다. 아래 명령으로 확인 가능하다.
# java 7 이하
java -XX:+PrintFlagsFinal -version 2>&1 | grep -i -E 'heapsize|permsize|version'
# java 8 이상
java -XX:+PrintFlagsFinal -version 2>&1 | grep -i -E 'heapsize|metaspace|version'
 
uintx InitialHeapSize                          := 522190848                           {product}
uintx MaxHeapSize                              := 8355053568                          {product}
  • 혹은 Java 애플리케이션에서 확인도 가능하다.
System.out.println(Runtime.getRuntime().maxMemory());

64Bit 운영체제에서 포인터 크기

  • 64Bit 운영체제에서라도 64bit long이 아니라 기본적으로 32bit 포인터를 사용한다.
  • 그러나 Heap 사이즈가 대략 32GB가 넘어가면 그때부터 64bit 포인터로 바뀐다. 이렇게 되면 포인터 자체가 차지하는 메모리가 너무 커서 낭비적이 될 수 있다.
  • 일반적으로 Heap Size가 31GB 정도일 때 32bit 압축 포인터가 거의 확실하게 사용된다. 그 이상일 때는 운영체제 환경, JDK 종류/버전 등에 따라 64bit가 사용될 가능성도 있다.
  • Oracle(SUN) JVM Options에서 UseCompressedOops=true 일 때 참조.

Heap Dump on OutOfMemoryError

  • Java 시작시에 다음과 같은 옵션을 주면, OutOfMemoryError로 인한 JVM에 종료시 Heap Dump를 생성해준다.
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof
  • -XX:HeapDumpPath를 생략하면 JVM 시작 디렉토리에 java_pid<pid>.hprof형태로 생성된다.
  • -XX:+PrintClassHistogramAfterFullGC, -XX:+PrintClassHistogramBeforeFullGC 등의 옵션으로 Full GC 전/후의 메모리 상태를 간략히 덤프할 수 있다. Java Garbage Collection 참조.

jmap

  • 힙 덤프(Heap Dump)를 뜬다.
  • WebServer - Heap Dump를 뜨는 작업은 매우 큰 용량과 시간을 필요로 하기 때문에 WebServer - WAS 구조일 때 WebServer를 내려서 WAS에 요청이 오지 않게 만든 뒤에 작업해야 한다.
  • live 옵션을 주면 live 객체만 덤프를 받는다. 이 옵션이 없으면 Heap 상의 모든 객체를 덤프 받는다.
  • 바이너리 파일로 덤프 받기
    # jmap 사용
    jps # Java PID 확인
    jmap -dump:live,format=b,file=파일명.bin <PID>
     
    # jmap 잘 안될때 force. live 허용 안됨.
    jmap -F -dump:format=b,file=파일명.bin <PID>
     
    # gcore 사용
    gcore -o 파일명.core <PID> # 파일명.core.<PID> 파일생성
    jmap -dump:format=b,file=파일명.bin /usr/java/defaults/bin/java 파일명.core
  • 한번에 확인해보기
    jps -v | grep "원하는검색어" | awk '{print $1}' | xargs jmap -dump:live,format=b,file=jdump.hprof
  • jstatd를 띄웠을 경우, <PID> 대신 <PID>@호스트네임:포트 형태로 호출해도 된다.
  • JVM 현재 메모리 상태 Foot print. 현재 메모리상의 클래스의 객체 갯수와 용량표시
    jmap -histo:live <PID>

jhat

  • 힙 덤프를 분석한다.
  • jhat -J-mx2048m 파일명.bin 실행후 http://localhost:7000/ 에서 살펴볼 수 있다.
    • 힘 덤프 파일의 크기가 클 경우 OOM 에러가 발생할 수 있으므로 -J-mx2048m 지정
  • 메모리를 너무 많이 먹어서 실제로 제대로 실행하기 힘듬.

MAT

용어

  • Shallow heap : 하나의 객체가 소비하는 메모리 용량. 객체 레퍼런스는 하나당 아키텍처에 따라 32bits(혹은 62bits)를 차지한다.
  • Ratained heap : 해당 객체의 모든 인스턴스를 GC했을 때 확보할 수 있는 메모리 총량.

IBM HeapAnalyzer

  • 힙덤프 파일의 크기가 크기 때문에 실행시 -Xmx2048m 형태로 메모리 옵션을 줘야 한다.

HPJmeter

  • GC Log, Heapdump 등을 분석할 수 있다.

jstat

현재 JVM의 메모리 상태를 확인해 볼 수 있다.

옵션명 내용
class 클래스 로더의 동작에 관한 통계 데이터
compiler HotSpot Just-in-Time 컴파일러의 동작에 관한 통계 데이터
gc GC된 heap의 동작에 관한 통계 데이터
gccapactiy 세대마다의 용량과 대응하는 영역에 관한 통계 데이터
gccause GC 통계 데이터의 개요(-gcutil 와 같다)와 직전 및 현재 (적용 가능한 경우)의 GC이벤트의 원인
gcnew New 세대의 동작에 관한 통계 데이터
gcnewcapacity New 세대의 사이즈와 대응하는 영역에 관한 통계 데이터
gcold Old 세대 및 Permanent 세대의 동작에 관한 통계 데이터
gcoldcapacity Old 세대의 사이즈에 관한 통계 데이터
gcpermcapacity Permanent 세대의 사이즈에 관한 통계 데이터
gcutil GC 통계 데이터의 개요
printcompilation HotSpot 컴파일 방법의 통계 데이터

SSH로 특정 서버의 GC 상태보기

서버가 여러대일 경우 특정 서버에 자동 접속하여 해당 서버의 Tomcat 인스턴스를 찾아서 gcutil 실행

ssh myhostname 'bash -s' <<'ENDSSH'
jstat -gcutil `jps | grep Bootstrap | awk '{print $1}'` 1s
ENDSSH

Code로 heap dump 뜨기

import javax.management.MBeanServer;
import java.lang.management.ManagementFactory;
 
import com.sun.management.HotSpotDiagnosticMXBean;
 
/**
 * 현재 Java Application의 Heap Dump를 뜨는 함수
 * @see <a href="https://blogs.oracle.com/sundararajan/programmatically-dumping-heap-from-java-applications">Programmatically dumping heap from Java applications</a>
 */
public class HeapDumper {
    // This is the name of the HotSpot Diagnostic MBean
    private static final String HOTSPOT_BEAN_NAME =
        "com.sun.management:type=HotSpotDiagnostic";
    // field to store the hotspot diagnostic MBean
    private static volatile HotSpotDiagnosticMXBean hotspotMBean;
 
    /**
     * Call this method from your application whenever you
     * want to dump the heap snapshot into a file.
     *
     * @param fileName name of the heap dump file
     * @param live     flag that tells whether to dump
     *                 only the live objects
     */
    public static void dumpHeap(String fileName, boolean live) {
        // initialize hotspot diagnostic MBean
        initHotspotMBean();
        try {
            hotspotMBean.dumpHeap(fileName, live);
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception exp) {
            throw new RuntimeException(exp);
        }
    }
 
    // initialize the hotspot diagnostic MBean field
    private static void initHotspotMBean() {
        if (hotspotMBean == null) {
            synchronized (HeapDumper.class) {
                if (hotspotMBean == null) {
                    hotspotMBean = getHotspotMBean();
                }
            }
        }
    }
 
    // get the hotspot diagnostic MBean from the
    // platform MBean server
    private static HotSpotDiagnosticMXBean getHotspotMBean() {
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            HotSpotDiagnosticMXBean bean =
                ManagementFactory.newPlatformMXBeanProxy(server,
                    HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class);
            return bean;
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception exp) {
            throw new RuntimeException(exp);
        }
    }
 
    public static void main(String[] args) {
        // default heap dump file name
        String fileName = "heap.hprof";
        // by default dump only the live objects
        boolean live = true;
        // simple command line options
        switch (args.length) {
            case 2:
                live = args[1].equals("true");
            case 1:
                fileName = args[0];
        }
        // dump the heap
        dumpHeap(fileName, live);
    }
}
java/memory.txt · 마지막으로 수정됨: 2022/01/26 14:47 저자 kwon37xi