====== Java Memory Analysis ====== * [[java:debug|Java Debug]] * [[java:gc|Java Garbage Collection]] * [[java:memory:mat|Java Memory Analyzer(MAT)]] * [[java:memory:heaphero|HeapHero]] * [[java:memory:jxray|JXRay]] * [[http://blog.naver.com/salsu0/30000025219|Java Heap 영역 설명]] ===== 기본 Heap 메모리 설정 ===== * https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html 참조. * 장비 메모리에서 OS와 다른 애플리케이션이 사용하는 것을 제외하고 설정할 수 있다.(보통 2GB정도 남겨두고 나머지를 설정하면 될듯) * 하지만 Lucene의 경우 off heap 메모리를 사용하기 때문에 ElasticSearch는 장비 메모리의 50%만 heap으로 설정함. off heap 사용시에 관련 사항 주의. * 운영체제 메모리가 아무리 많아도 heap은 32GB를 넘기지는 않게 한다. 31GB 정도 추천 * Linux 운영체제 swap 일어나는 것을 방지하기 위해 **swappiness=1** 로 조정한다. [[linux:performance|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()); * [[https://sarc.io/index.php/java/1092-jvm-default-heap-size|JVM의 default Heap Size가 궁금하세요?]] * [[https://mkyong.com/java/find-out-your-java-heap-memory-size|Find out your Java heap memory size - Mkyong.com]] * [[https://docs.oracle.com/en/java/javase/11/gctuning/ergonomics.html#GUID-DA88B6A6-AF89-4423-95A6-BBCBD9FAE781|Ergonomics]] * 기본 규칙(Java 11) * Min : 물리 메모리의 1/64 * Max : 물리 메모리의 1/4 ===== 64Bit 운영체제에서 포인터 크기 ===== * 64Bit 운영체제에서라도 64bit long이 아니라 기본적으로 32bit 포인터를 사용한다. * 그러나 Heap 사이즈가 **대략 32GB**가 넘어가면 그때부터 64bit 포인터로 바뀐다. 이렇게 되면 포인터 자체가 차지하는 메모리가 너무 커서 낭비적이 될 수 있다. * 일반적으로 Heap Size가 31GB 정도일 때 32bit 압축 포인터가 거의 확실하게 사용된다. 그 이상일 때는 운영체제 환경, JDK 종류/버전 등에 따라 64bit가 사용될 가능성도 있다. * [[java:options|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.hprof''형태로 생성된다. * ''-XX:+PrintClassHistogramAfterFullGC'', ''-XX:+PrintClassHistogramBeforeFullGC'' 등의 옵션으로 Full GC 전/후의 메모리 상태를 간략히 덤프할 수 있다. [[java: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 # jmap 잘 안될때 force. live 허용 안됨. jmap -F -dump:format=b,file=파일명.bin # gcore 사용 gcore -o 파일명.core # 파일명.core. 파일생성 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 * [[java:jstatd|jstatd]]를 띄웠을 경우, '''' 대신 ''@호스트네임:포트'' 형태로 호출해도 된다. * JVM 현재 메모리 상태 Foot print. 현재 메모리상의 클래스의 객체 갯수와 용량표시 jmap -histo:live ===== jhat ===== * 힙 덤프를 분석한다. * ''jhat -J-mx2048m 파일명.bin'' 실행후 http://localhost:7000/ 에서 살펴볼 수 있다. * 힘 덤프 파일의 크기가 클 경우 OOM 에러가 발생할 수 있으므로 ''-J-mx2048m'' 지정 * 메모리를 너무 많이 먹어서 실제로 제대로 실행하기 힘듬. * [[https://blogs.oracle.com/sundararajan/entry/querying_java_heap_with_oql|Querying Java heap with OQL ]] ===== MAT ===== * [[java:memory:mat|Java Memory Analyzer(MAT)]] ==== 용어 ==== * Shallow heap : 하나의 객체가 소비하는 메모리 용량. 객체 레퍼런스는 하나당 아키텍처에 따라 32bits(혹은 62bits)를 차지한다. * Ratained heap : 해당 객체의 모든 인스턴스를 GC했을 때 확보할 수 있는 메모리 총량. ===== IBM HeapAnalyzer ===== * [[https://www.ibm.com/developerworks/community/groups/service/html/communityview?communityUuid=4544bafe-c7a2-455f-9d43-eb866ea60091|IBM HeapAnalyzer]] * 힙덤프 파일의 크기가 크기 때문에 실행시 ''-Xmx2048m'' 형태로 메모리 옵션을 줘야 한다. ===== HPJmeter ===== * [[https://h20392.www2.hp.com/portal/swdepot/displayProductInfo.do?productNumber=HPJMETER|HPJmeter]] * GC Log, Heapdump 등을 분석할 수 있다. ===== jstat ===== 현재 JVM의 메모리 상태를 확인해 볼 수 있다. * [[http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html|jstat]] * [[http://sync.egloos.com/3322205|jstat 사용법]] * [[http://5dol.tistory.com/182|Java jstat로 메모리 모니터링]] * gc 예 jps # PID확인 jstat -gc 1000 # 1초마다 gc 확인 * jstat options ^ 옵션명 ^내용 ^ | 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 뜨기 ===== * [[https://blogs.oracle.com/sundararajan/programmatically-dumping-heap-from-java-applications|Programmatically dumping heap from Java applications]] import javax.management.MBeanServer; import java.lang.management.ManagementFactory; import com.sun.management.HotSpotDiagnosticMXBean; /** * 현재 Java Application의 Heap Dump를 뜨는 함수 * @see Programmatically dumping heap from Java applications */ 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); } }