
Docker 환경에서 자바 개발자를 괴롭히는 네이티브 메모리 누수를 추적하고 해결하는 실전 워크플로우
본 아티클은 JVM 힙 모니터링의 한계를 넘어, Docker 컨테이너 환경에서 발생하는 네이티브 메모리 누수 문제를 해결하는 과정을 다룹니다. jemalloc과 async-profiler를 조합해 힙 외부의 메모리 할당 원인을 규명하고, 실무에서 놓치기 쉬운 자원 해제 패턴과 메모리 할당기 튜닝법을 상세히 가이드합니다.
Docker 기반의 Java 서비스를 운영하며 원인 모를 OOM 현상을 겪고 있다면, 반드시 NMT와 jemalloc 프로파일링을 병행해보길 권장합니다. 특히 리소스 스트림이나 압축 관련 클래스 사용 시 명시적인 자원 해제 처리를 최우선으로 검토하십시오.
JVM 힙 메모리는 정상적으로 관리되고 있음에도 불구하고 Docker 컨테이너의 RSS가 지속적으로 상승하여 리눅스 OOM Killer에 의해 프로세스가 종료되는 현상이 발생했습니다. 이는 NMT(Native Memory Tracking)로도 추적되지 않는 영역에서 발생하는 메모리 할당으로 인해 자바 개발자가 인지하기 어려운 사각지대였습니다.
네이티브 메모리 할당 추적을 위해 jemalloc과 jeprof를 사용하여 네이티브 콜그래프를 분석하였고, async-profiler의 '-e alloc' 옵션을 통해 해당 할당을 유발한 자바 코드 경로를 매핑했습니다. 이를 통해 JAR 리소스를 읽을 때 명시적으로 닫지 않은 Inflater 객체가 네이티브 zlib 버퍼를 해제하지 못하고 누적됨을 확인했습니다.
리소스 스트림을 명시적으로 닫는 코드 수정만으로 RSS가 기준치(1328MB) 대비 절반 이하인 516MB로 감소하는 근본적인 해결 결과를 얻었습니다. 또한 glibc의 arena 튜닝이나 jemalloc 교체 등 인프라 레벨의 완화책들을 수치 기반으로 검증하여 상황별 최적의 메모리 관리 방안을 도출했습니다.
Trade-off
jemalloc 교체는 메모리 파편화 완화에 효과적이지만, Docker 컨테이너 환경에서는 기본 설정 시 오히려 dirty page를 점유하여 RSS가 증가할 수 있으므로 'dirty_decay_ms'와 같은 추가 튜닝이 필수적입니다.
프로세스가 실제 물리 메모리(RAM)에 점유하고 있는 용량을 의미하며, JVM 힙 외에도 스택, 코드, 네이티브 라이브러리 할당 영역을 포함합니다.
멀티스레드 환경에서 메모리 파편화를 줄이고 할당 속도를 높이기 위해 설계된 범용 메모리 할당기입니다.
자바에서 ZIP 파일이나 압축된 데이터를 풀 때 사용하는 클래스로, 내부적으로 네이티브 라이브러리인 zlib를 호출합니다.




