Core Dump, Heap Dump, Thread Dump

de_sj_awa·2021년 4월 19일
1
post-custom-banner

1. Core Dump

코어 덤프(core dump) = 메모리 덤프(memory dump) = 시스템 덤프(system dump)

코어 덤프(core dump)는 컴퓨터 프로그램이 특정 시점에 작성 중이던 메모리 상태를 기록한 것으로, 보통 프로그램이 비정상적으로 종료했을 때 만들어진다. 실제로는 그 외에 중요한 프로그램 상태도 같이 기록되곤 하는데, 프로그램 카운터, 스택 포인터 등 CPU 레지스터나, 메모리 관리 정보, 그 외 프로세서 및 운영 체제 플래그 및 정보 등이 포함된다. 코어 덤프는 프로그램 오류 진단과 디버깅에 쓰인다.

  • 리눅스에서 gcore라는 명령어를 사용해서 코어 덤프를 남길 수 있다.
  • JVM은 hs_err_pid.log라는 파일을 남기고 죽는다.
  • java에서 1GB의 메모리를 사용하면 코어 덤프는 수십기가에 달하는 파일을 생성한다.
  • 아무 근거 없이 죽었다면 kill -9 <pid>로 죽었거나 segfault와 같이 프로세스 내의 오류로 죽었을 가능성이 있다.
    -> var/log messages 로그를 봐야한다.
  1. 코어 덤프 자동으로 생성하게 만들기
  • ulimit -a 명령어로 서버 설정을 확인한다.
  • core file size(blocks, -c) 0 <- 이처럼 0이면 core dump는 안남는다.
  • ulimit -c unlimited <-core dump를 남기도록 변경한다.
  1. 코어 덤프 분석하기
  • gdb 프로그램을 사용한다.
  • gdb/자바실행파일 Full path/java core.pid <- 실행
  • 인터프리터 방식으로 이 툴을 사용할 수 있다.
  1. 명령어
  • bt
  • info thread
  • thread 쓰레드번호
  • where
  • x/i 메모리 주소값

2. Heap Dump

Heap Dump는 현재 Heap에서 점유되고 있는 객체들에 대한 조사를 위해 필요하다.

  • JVM GC 블로킹에서 Old 영역을 많이 사용할 경우 일반적으로 사용되지 않는 객체들이 Reference되어 GC되지 않고 남아 있을 가능성이 높다. 이때는 어떤 객체들이 많이 점유되고 있는지 조사할 필요가 있다.
  • Old 영역을 많이 점유하고 있으면 Full GC가 자주 오래동안 발생할 수 있으므로 업무 응답시간에 문제 또는 장애를 일으킬 소지가 있다.

1. Heap Dump on OutOfMemoryError

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

2. 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 &lt;pid&gt # 파일명.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>

3. jhat

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

4. MAT

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

5. IBM HeapAnalyzer

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

6. HPJmeter

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

7. jstat

  • 현재 JVM의 메모리 상태를 확인해볼 수 있다.
  • gc 예
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

3. Thread Dump

Thread Dump는 현재 수행중인 쓰레드에 대한 호출 경로 StackTrace를 보기 위해 필요하다.

쓰레드 덤프로 확인할 수 있는 상황

  • 모든 시스템에 응답이 없을 때
  • 사용자 수가 많지 않은데 cpu 사용량이 높을 때
  • 특정 어플리케이션 수행 시 응답이 없을 때
  • 서비스 실행시간이 길어질 수록 응답시간이나 CPU 사용량이 늘어날 때
  • java.lang.Thread를 이용해서 직접 개발하지 않았는데 이미 프레임워크, WAS, 라이브러리를 사용하고 있을 때 내부적으로 문제가 생길 때
  1. 명령어를 통해 프로세스의 THREAD 단위로 CPU 사용률을 모니터링하여 CPU율을 모니터링하여 CPU를 많이 차지하는 THREAD 확인
$ 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. THREAD DUMP 생성

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"

4. Heap Dump / Thread Dump 분석 툴

  1. fastThread
    • 덤프 파일을 올리면 분석해줌
  2. IntelliJ IDEA
    • Ultimate 버전에서만 가능
  3. THREADLOGIC.JAR
  4. VisualVM
    • JDK에 기본으로 들어있음
  5. MAT(Memory Analyzer)
  6. IBM HeapAnalyzer
  7. GCEASY

참고

profile
이것저것 관심많은 개발자.
post-custom-banner

0개의 댓글