ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Dump File에 대해서
    Java 2024. 10. 31. 23:31
    힙 덤프를 분석하는 AI

    자바의 덤프 파일이란?

    덤프 파일(Dump File)이란 자바 애플리케이션에서 문제를 진단하고 디버깅하는 데 사용되는 파일이다. 덤프 파일은 JVM의 상태가 담겨있어, 시스템 오류가 발생했을 때 그 문제의 원인을 파악하고 해결하는 데 도움을 준다. 이러한 덤프 파일에는 몇 가지 종류가 있다. 함께 알아보도록 하자.
     

    (1) 힙 덤프 (Heap Dump)

    힙 덤프란 JVM 힙 메모리의 상태를 캡처한 파일로, 모든 객체와 그 참조 관계가 포함되어 있다. 힙 덤프는 주로 메모리 누수 분석, GC 튜닝, 객체 사용 패턴을 파악하는 데 사용된다. 일반적으로 .hprof 확장자를 가지며 jmap, jvisualvm, MAT(Memory Analyzer Tool) 같은 툴을 통해 분석할 수 있다.
     

    (2) 쓰레드 덤프 (Thread Dump)

    쓰레드 덤프란 JVM에 존재하는 모든 쓰레드의 스택 트레이스가 담긴 파일이다. 주로 멀티쓰레딩 문제 진단, 데드락 탐지, CPU 사용량 분석을 위해 사용된다. 주로 텍스트 파일로 생성된다.
     

    (3) 코어 덤프 (Core Dump)

    코어 덤프란 JVM 프로세스의 전체 메모리 상태를 캡처한 파일이며 운영체제에서 생성된다. 코어 덤프를 통해 JVM 자체의 치명적인 오류나 네이티브 코드와의 연동 문제를 진단할 수 있다. 파일 포맷은 일반적으로 운영체제에 따라 다르며, C/C++ 디버깅 도구(gdb 등)로 분석할 수 있다.
     

    위에서 소개한 덤프 파일 중 주로 많이 사용되는 것은 힙 덤프쓰레드 덤프이다. 실무 환경과 같은 엔터프라이즈 환경에서는 메모리와 쓰레드 이슈가 빈번하게 발생하기 때문이다. 이제부터는 힙 덤프를 실제로 생성하여 확인해보자!

    힙 덤프는 힙 메모리의 상태를 캡처한 파일이라고 했다. 힙 메모리는 애플리케이션 실행 간 생성되는 객체가 저장되는 곳이며 애플리케이션이 시작할 때 그 크기가 고정된다. GC에 의해 힙 메모리 관리가 이루어지지만, 메모리가 회수되지 않거나 계속해서 메모리 공간을 차지하는 객체들이 쌓이다가 최대 한도에 도달하고, 더 이상 새로운 객체를 할당할 메모리가 부족할 때 OutOfMemoryError(OOM)가 발생한다.
     
     
    의도적으로 OOM을 발생시키는 코드를 작성하고 힙 덤프를 생성해보자. 이 코드가 OOM을 유발하는 이유는 맨 마지막에 다룬다.

     public class OOMHeapDumpExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            while (true) {
                list.add(new String("OOM TEST"));
            }
        }
    }

     
     
    인텔리제이 [Run Configurations]에서 다음과 같은 VM 옵션을 추가하여 힙 덤프를 생성할 수 있다.

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof -Xmx64m
    VM 옵션 추가
    • -XX:+HeapDumpOnOutOfMemoryError : OutOfMemoryError가 발생할 때 자동으로 힙 덤프를 생성
    • -XX:HeapDumpPath=./heapdump.hprof : 현재 프로젝트 경로에 heapdump.hprof 파일로 힙 덤프를 저장
    • -Xmx64m : JVM 최대 힙 메모리를 64MB로 제한하여 메모리 부족 상황을 유도

     
     
    실행 결과

    힙 덤프 생성
    • java.lang.OutOfMemoryError: Java heap space : OOM 발생 (메모리 공간이 없는 상황에 객체를 생성하려고 하는 순간 - 10번째줄)
    • Dumping heap to ./heapdump.hprof ... : 힙 덤프 생성 중 ...
    • Heap dump file created [83111074 bytes in 0.198 secs] : 힙 덤프 생성 완료. 힙 덤프 크기와 생성하는 데 걸린 시간
    • Exception in thread "main" : 스택 트레이스 출력
    • Process finished with exit code 1 : 애플리케이션이 오류로 인해 비정상 종료되었음을 의미

     
     
    덤프 파일 삭제 후 try-catch 추가한 뒤 재실행

    • 덤프 파일이 재생성되었다.
    • catch 구문에서 Throwable 하위 클래스는 모두 잡을 수 있다.
    • 클래스 상속 구조 : OutOfMemoryError < VirtualMachineError < Error < Throwable
    • 하지만 정말 OOM Error를 catch 했다고 볼 수 있을까? catch한 것처럼 보이지만, 엄밀히 말하면 OOM이 발생하고 난 뒤에 catch 되는 것이다. 그래서 Error를 개발자가 직접 해결할 수 없는 심각한 시스템 수준의 오류라고 하는 것이다.
    • 이러한 이유로 catch 구문에서는 Exception 하위 클래스만 처리하는 것이 일반적이며 Error의 경우는 코드 수정이나 시스템 설정을 통해 해결해야 한다.

     
     
    자, 이제 VisualVM을 통해 생성된 힙 덤프를 확인해보자. VisualVM을 실행하고 힙 덤프를 드래그 앤 드롭 하여 확인할 수 있다.

    VisualVM Summary Tab

    간단하게 OOM을 발생시킨 상황이라 유의미한 정보는 파악할 수 없고, 나 또한 아직 그럴 능력이 되지는 않지만 String 객체 때문에 문제가 됐다는 것은 확인할 수 있을 것이다 ㅋㅋ 덤프 파일 분석은 나중에 좀 더 공부해서 자세히 다뤄 봐야겠다.


    그럼 이제 마지막으로 예제 코드에서 OOM이 발생한 이유를 알아보고 마무리하자.

    public class OOMHeapDumpExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            while (true) {
                list.add(new String("OOM TEST"));
            }
        }
    }

     
    1. 무한 루프
    계속해서 new String()을 통해 객체를 생성하여 list에 추가하고 있기 때문에 점점 힙 메모리가 고갈된다.
     
    2. GC가 관리하지 않음
    가비지 컬렉터는 더 이상 사용되지 않는 객체를 메모리에서 해제하여 힙 메모리를 확보하는 역할을 한다. 하지만 이 예제에서는 GC가 메모리를 정리해주지 않는다. 왜냐하면 GC는 아직 필요하다고 판단되는 객체는 정리하지 않기 때문이다. 이를 다음과 같은 순서로 이해해보자.

    • List<String> 객체가 생성됨
    • 계속해서 List에 String 객체를 추가함 (프로그램 로직에서 계속 사용 중 -> GC가 List를 필요한 객체라고 판단)
    • String 객체를 생성할 때 new 연산자를 사용하므로 매번 새로운 String 객체가 힙 메모리에 저장됨 (메모리 고갈 속도 증가)
    • 지속적으로 쌓이는 String 객체는 List가 참조하고 있고, 그 List를 프로그램에서 계속 사용 중이니 GC가 메모리를 정리할 수 없음