메모리 분석기

다운로드 0
업데이트 2025. 11. 15.

사용 매뉴얼

기존에는 힙 덤프가 생성되면 즉시 분석할 수 없어서 힙 덤프 파일을 반출하여 Eclipse Memory Analyzer로 분석하는 과정을 거쳤습니다. 그러나 일반적으로 힙 메모리 덤프는 파일 크기가 최소 GB 단위로 매우 거대하기 때문에 온프레미스 사이트에서 반출하기에는 시간도 많이 소요되고 승인을 받는데도 어려움이 많았습니다.

메모리 분석기 앱은 이런 문제를 해결하기 위해 개발되었고, 약 2MB에 불과한 크기로 핵심적인 힙 덤프 분석을 수행할 수 있습니다. 이 문서에서는 힙 메모리 고갈로 인한 GC 장애가 발생했을 때 힙 덤프를 생성한 이후 어떻게 분석을 진행하는지 설명합니다.

아래 예제는 SNR3067 이슈 개발 도중에 발생했던 실제 메모리 누수에 대한 분석 예시입니다.

Note
MAT 앱의 모든 확장 쿼리 명령어는 클러스터 관리자 권한을 요구합니다.

인덱스 생성

HPROF 파일은 일반적으로 수십 GB의 매우 큰 파일이기 때문에 인덱스를 생성하지 않으면 분석이 어렵습니다. HPROF 파일 경로에 대해 아래와 같이 쿼리하여 인덱스를 생성할 수 있습니다.

mat-build SNR3067.hprof

HPROF 인덱스 생성

percent 값이 100까지 출력되고 Index generation completed successfully 메시지가 출력되면 인덱스 빌드에 성공한 것입니다. 이 때 서버에는 아래와 같은 파일들이 HPROF 파일과 같은 위치에 생성됩니다.

파일 확장자유형설명
SNR3067.idx.indexObject ID to Address객체 ID → 메모리 주소 매핑
SNR3067.o2hprof.indexObject to HPROF Position객체 ID → HPROF 파일 내 위치
SNR3067.o2c.indexObject to Class객체 ID → 클래스 ID 매핑
SNR3067.a2s.indexArray to Size배열 객체 → 크기 매핑
SNR3067.o2ret.indexObject to Retained Heap객체 ID → Retained Heap 크기
SNR3067.domIn.indexDominator InboundDominator 관계 (자식 → 부모)
SNR3067.domOut.indexDominator OutboundDominator 관계 (부모 → 자식들)
SNR3067.i2sv2.indexIndex to String Value v2java.lang.String 객체의 실제 문자열 값 캐시
SNR3067.inbound.index 참조 관계 (피참조자 → 참조자들)
SNR3067.outbound.index 참조 관계 (참조자 → 피참조자들)
SNR3067.index 스냅샷의 전체 메타정보. 클래스 정의, 객체 개수, 힙 크기 등
SNR3067.threads 스레드 정보

상위 메모리 점유 객체 분석

메모리 누수가 발생했다면 소수의 객체가 매우 큰 메모리를 점유하게 됩니다. mat-dominators 쿼리 명령어를 사용하면 메모리 점유 상위 객체를 빠르게 분석할 수 있습니다.

mat-dominators SNR3067.hprof

상위 메모리 점유 객체 분석

위의 결과를 보면 org.araqne.pool.generic.GenericPool @ 0x4009aaa9000이 무려 83.105%의 비율을 차지하고 있음을 즉각 확인할 수 있습니다. 트리를 내려가면서 살펴보면 Rex$RegexContext가 메모리 풀을 상당수 점유했을 것이라 추정할 수 있습니다. 트리에서는 대표 케이스만 추출하기 때문에, Rex$RegexContext의 비율이 0.014%로 표시되더라도 실제로는 매우 많은 객체가 풀을 점유하고 있을 수 있습니다. 이를 검증하려면 이제 히스토그램을 봐야합니다.

히스토그램 분석

mat-histogram SNR3067.hprof

히스토그램 분석

org.araqne.logdb.query.command.Rex$RegexContext은 96606개로 34위이기 때문에 스크린샷에서는 잘렸습니다. 주목할 것은 14위의 com.google.re2j.Inst 클래스입니다. 일반적으로 JDK 내장 클래스는 직접적인 원인이 아니기 때문에, java.lang.Object[]나 byte[] 등은 무시하고, com.logpresso.tap.impl.session_t[]org.araqne.storage.api.RCDirectBuffer 등을 주목하게 됩니다. 다만 session_t나 RCDirectBuffer는 이전 버전에서도 정상 동작 시 점유하던 비율이기 때문에 건너뛰고, com.google.re2j.Inst를 주목하게 되는 것입니다.

클래스 GC 경로 분석

mat-class2gc 쿼리 명령어는 지정된 클래스 인스턴스를 샘플링(기본 10개)하여 어떤 객체에 매달려있는지 가시화합니다.

mat-class2gc class="com.google.re2j.Inst" SNR3067.hprof

클래스 GC 경로 분석

com.google.re2j.Inst 객체는 실제 스트림 쿼리 (com.logpresso.query.engine.StreamQuery)에서 사용하는 rex 쿼리 명령어 (org.araqne.logdb.query.command.Rex) 객체의 오브젝트 풀에 매달려있는 것을 확인할 수 있습니다.

객체 GC 경로 분석

mat-path2gc 쿼리 명령어를 사용하면 특정 메모리 주소의 인스턴스에 대해 GC 경로를 좀 더 상세하게 확인할 수 있습니다.

mat-path2gc address="0x4003b83eb78" SNR3067.hprof

객체 GC 경로 분석

지정된 주소의 객체로부터 참조하는 객체를 찾아올라가기 때문에, 트리에서는 내려가는 방향이지만 실제로는 상위의 객체에 해당합니다. retained_heap이 폭증하는 구간이 일반적으로 문제 지점입니다.

객체 정보 분석

구체적으로 어떤 rex 쿼리 명령어가 문제되었는지 확인하려면 객체 주소를 지정하여 모든 필드 값을 확인할 수 있습니다.

mat-attributes address="0x4001ea83088" SNR3067.hprof

객체 필드 목록 조회

이제 명확하게 아래 정규식을 포함한 rex 구문에서 문제가 발생했음을 확인할 수 있습니다.

(?:Login from: (?<src_ip>[^,]+), Source region: (?<country>[^,]+), User name: (?<user>[^,]+)(?:, )?
Client OS version: (?<client_os>[^\"]+), Reason: (?<reason>.*?), Auth type: (?<auth_type>.*)\.\")
|(?:Login from: (?<src_ip>[^,]+), Source region: (?<country>[^,]+), User name: (?<user>[^,]+), 
Auth type: (?<auth_type>[^,]+).Client OS version: (?<client_os>[^\"]+))

후속 대응

1차적으로 위와 같이 분석한 정보를 로그프레소 지원 포탈을 통해 전달해주시면 장애 진단 및 패치를 빠르게 진행할 수 있습니다. 현장에서 정보 추출에 어려움이 있는 경우 로그프레소 기술지원팀의 도움을 받으시기 바랍니다.