====== 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);
}
}