코어 덤프(core dump)
는 컴퓨터 프로그램이 특정 시점에 작성 중이던 메모리 상태를 기록한 것으로, 보통 프로그램이 비정상적으로 종료했을 때 만들어진다. 실제로는 그 외에 중요한 프로그램 상태도 같이 기록되곤 하는데, 프로그램 카운터, 스택 포인터 등 CPU 레지스터나, 메모리 관리 정보, 그 외 프로세서 및 운영 체제 플래그 및 정보 등이 포함된다. 코어 덤프는 프로그램 오류 진단과 디버깅에 쓰인다.
gcore
라는 명령어를 사용해서 코어 덤프를 남길 수 있다.Heap Dump는 현재 Heap에서 점유되고 있는 객체들에 대한 조사를 위해 필요하다.
OutOfMemoryError
로 이한 JVM에 종료시 Heap Dump를 생성해준다.-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof
-XX:HeapDumpPath
를 생략하면 JVM 시작 디렉토리에 java_pid<pid>.hprof
형태로 생성된다.-XX:+PrintClassHistogramAfterFullGC
, -XX:+PrintClassHistogramBeforeFullGC
등의 옵션으로 Full GC 전/후의 메모리 상태를 간략히 덤프할 수 있다.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>@호스트네임:포트 형태로 호출해도 된다.jmap -histo:live <PID>
jhat -J-mx2048m 파일명.bin
실행 후 http://localhost:7000/에서 살펴볼 수 있다.jps # PID 확인
jstat -gc <PID> 1000 # 1초마다 gc 확인
jstat options
옵션명 | 내용 |
class | 클래스 로더의 동작에 관한 통계 데이터 |
compiler | HotSpot Jus-In-Time 컴파일러의 동작에 관한 통계 데이터 |
gc | GC된 heap의 동작에 관한 통계 데이터 |
gccapacity | 세대마다 용량과 대응하는 영역에 관한 통계 데이터 |
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 뜨기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 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); } } | cs |
Thread Dump는 현재 수행중인 쓰레드에 대한 호출 경로 StackTrace를 보기 위해 필요하다.
쓰레드 덤프로 확인할 수 있는 상황
$ prstat -L -m -p [프로세스ID] [Intervall]
가장 높은 cpu를 차지하는 thread의 twpid를 16진수로 변환(=threadDump의 nid)
또는 아래의 명령어 사용 가능
$ ps -mo pid,lwd,stime,time,cpu -C java
가장 높은 cpu를 차지하는 thread의 TID를 16진수로 변환(=thread Dump의 native ID)
1) pid 확인
$ ps -eF | grep java
$ jps -mlv
혹은
$ sudp ps -aux | grep java
2) JCMD 이용
$ cd /원하는 폴더
$ jcmd ${pid} Thread.print > #{fileName}.print
3) kill -3 이용
$ kill -3 pid
4) JSTACK 이용
$ jstack <pid> <filename>
$ jstack -l <pid> > <file-path>
JSTACK 스크립트
#!/usr/bin/env bash
date_postfix=$(date +"%Y-%m-%dT%H:%M:%S.txt")
file_name=$date_postfix
top_process_info=$(ps aux --sort=-pcpu | head -n 2 | grep -v PID)
i=0
user='null'
pid='null'
cmd=''
for token in $top_process_info
do
if [ $i == 0 ]; then
user=$token
fi
if [ $i == 1 ]; then
pid=$token
fi
if [ $i -ge 10 ]; then
cmd=$cmd' '$token
fi
i=$(($i + 1))
done
top_thread_info=$(top -H -b -n1 -p "$pid" | head -n 8 | tail -n 1 | grep -v PID)
i=0
tid='null'
pcpu='null'
for token in $top_thread_info
do
if [ $i == 0 ]; then
tid=$token
fi
if [ $i == 8 ]; then
pcpu=$token
fi
i=$(($i + 1))
done
tid_hex=$(printf 'nid=0x%x\n' $tid)
thread_dump=$(sudo -u "$user" -H sh -c "/${JAVA_PATH}/bin/jstack -l $pid" | grep $tid_hex -A 100 )
echo "[command] $cmd" >> "$file_name"
echo "[top_thread] $tid" >> "$file_name"
echo "[top_thread_hex] $tid_hex" >> "$file_name"
echo "[top_thread_pcpu] $pcpu" >> "$file_name"
echo "$thread_dump" >> "$file_name"
date_postfix=$(date +"_%Y-%m-%dT%H:%M:%S.txt")
ps_file_name='ps'$date_postfix
top_file_name='top'$date_postfix
td_file_name='td'$date_postfix
top_process_id=$(ps aux --sort=-pcpu | head -n 2 | grep -v PID | awk '{print $2}')
user=$(ps -p "$top_process_id" -o user | grep -v USER)
ps_info_columns='UID PID PPID LWP C NLWP STIME TTY TIME CMD'
ps_info=$(ps -eLf --sort=-pcpu | grep "$top_process_id")
top_info=$(top -H -b -n1 -p "$top_process_id")
echo "$ps_info_columns" >> "$ps_file_name"
echo "$ps_info" >> "$ps_file_name"
echo "$top_info_columns" >> "$top_file_name"
echo "$top_info" >> "$top_file_name"
top_dump=$(sudo -u "$user" -H sh -c "/${JAVA_PATH}/bin/jstack -l $top_process_id")
echo "$top_dump" >> "$td_file_name"
참고