사용 매뉴얼
기존에는 힙 덤프가 생성되면 즉시 분석할 수 없어서 힙 덤프 파일을 반출하여 Eclipse Memory Analyzer로 분석하는 과정을 거쳤습니다. 그러나 일반적으로 힙 메모리 덤프는 파일 크기가 최소 GB 단위로 매우 거대하기 때문에 온프레미스 사이트에서 반출하기에는 시간도 많이 소요되고 승인을 받는데도 어려움이 많았습니다.
메모리 분석기 앱은 이런 문제를 해결하기 위해 개발되었고, 약 2MB에 불과한 크기로 핵심적인 힙 덤프 분석을 수행할 수 있습니다. 이 문서에서는 힙 메모리 고갈로 인한 GC 장애가 발생했을 때 힙 덤프를 생성한 이후 어떻게 분석을 진행하는지 설명합니다.
아래 예제는 SNR3067 이슈 개발 도중에 발생했던 실제 메모리 누수에 대한 분석 예시입니다.
인덱스 생성
HPROF 파일은 일반적으로 수십 GB의 매우 큰 파일이기 때문에 인덱스를 생성하지 않으면 분석이 어렵습니다. HPROF 파일 경로에 대해 아래와 같이 쿼리하여 인덱스를 생성할 수 있습니다.
percent 값이 100까지 출력되고 Index generation completed successfully 메시지가 출력되면 인덱스 빌드에 성공한 것입니다. 이 때 서버에는 아래와 같은 파일들이 HPROF 파일과 같은 위치에 생성됩니다.
| 파일 확장자 | 유형 | 설명 |
|---|---|---|
| SNR3067.idx.index | Object ID to Address | 객체 ID → 메모리 주소 매핑 |
| SNR3067.o2hprof.index | Object to HPROF Position | 객체 ID → HPROF 파일 내 위치 |
| SNR3067.o2c.index | Object to Class | 객체 ID → 클래스 ID 매핑 |
| SNR3067.a2s.index | Array to Size | 배열 객체 → 크기 매핑 |
| SNR3067.o2ret.index | Object to Retained Heap | 객체 ID → Retained Heap 크기 |
| SNR3067.domIn.index | Dominator Inbound | Dominator 관계 (자식 → 부모) |
| SNR3067.domOut.index | Dominator Outbound | Dominator 관계 (부모 → 자식들) |
| SNR3067.i2sv2.index | Index to String Value v2 | java.lang.String 객체의 실제 문자열 값 캐시 |
| SNR3067.inbound.index | 참조 관계 (피참조자 → 참조자들) | |
| SNR3067.outbound.index | 참조 관계 (참조자 → 피참조자들) | |
| SNR3067.index | 스냅샷의 전체 메타정보. 클래스 정의, 객체 개수, 힙 크기 등 | |
| SNR3067.threads | 스레드 정보 |
상위 메모리 점유 객체 분석
메모리 누수가 발생했다면 소수의 객체가 매우 큰 메모리를 점유하게 됩니다. mat-dominators 쿼리 명령어를 사용하면 메모리 점유 상위 객체를 빠르게 분석할 수 있습니다.
위의 결과를 보면 org.araqne.pool.generic.GenericPool @ 0x4009aaa9000이 무려 83.105%의 비율을 차지하고 있음을 즉각 확인할 수 있습니다. 트리를 내려가면서 살펴보면 Rex$RegexContext가 메모리 풀을 상당수 점유했을 것이라 추정할 수 있습니다. 트리에서는 대표 케이스만 추출하기 때문에, Rex$RegexContext의 비율이 0.014%로 표시되더라도 실제로는 매우 많은 객체가 풀을 점유하고 있을 수 있습니다. 이를 검증하려면 이제 히스토그램을 봐야합니다.
히스토그램 분석
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개)하여 어떤 객체에 매달려있는지 가시화합니다.
com.google.re2j.Inst 객체는 실제 스트림 쿼리 (com.logpresso.query.engine.StreamQuery)에서 사용하는 rex 쿼리 명령어 (org.araqne.logdb.query.command.Rex) 객체의 오브젝트 풀에 매달려있는 것을 확인할 수 있습니다.
객체 GC 경로 분석
mat-path2gc 쿼리 명령어를 사용하면 특정 메모리 주소의 인스턴스에 대해 GC 경로를 좀 더 상세하게 확인할 수 있습니다.
지정된 주소의 객체로부터 참조하는 객체를 찾아올라가기 때문에, 트리에서는 내려가는 방향이지만 실제로는 상위의 객체에 해당합니다. retained_heap이 폭증하는 구간이 일반적으로 문제 지점입니다.
객체 정보 분석
구체적으로 어떤 rex 쿼리 명령어가 문제되었는지 확인하려면 객체 주소를 지정하여 모든 필드 값을 확인할 수 있습니다.
이제 명확하게 아래 정규식을 포함한 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차적으로 위와 같이 분석한 정보를 로그프레소 지원 포탈을 통해 전달해주시면 장애 진단 및 패치를 빠르게 진행할 수 있습니다. 현장에서 정보 추출에 어려움이 있는 경우 로그프레소 기술지원팀의 도움을 받으시기 바랍니다.





