네이티브 메모리란?
네이티브 메모리는 운영체제가 프로세스에 직접 할당하는 물리적 또는 가상 메모리 영역을 의미한다. 자바 애플리케이션은 JVM 내에서 실행되며, 이는 힙(Heap)과 같은 관리 메모리 외에도 VM 내부 구조 및 네이티브 코드를 위해 별도의 시스템 메모리를 사용한다. 이러한 비관리형 메모리는 자바 힙 덤프나 GC 로그로는 확인할 수 없으므로, 성능 문제 진단 시 별도의 도구가 필요하다.
주요 구성 요소
- 스택(Stack): 각 스레드의 호출 스택과 지역 변수 저장 공간. 고정 크기로 할당되며, 스레드 수에 따라 총량 증가.
- 힙(Heap): 객체 인스턴스가 동적으로 할당되는 공간. GC에 의해 회수됨.
- 코드 캐시(Code Cache): JIT 컴파일된 네이티브 머신 코드 저장 영역.
- 메타스페이스(Metaspace): 클래스 메타데이터 저장소. 이전 버전의 PermGen 영역을 대체함.
- G1GC 관련 영역: G1 수집기 사용 시 리전 관리, 태그 스택, RSet 등을 위한 예약 메모리.
- 공유 라이브러리 맵핑: JDK 모듈 및 네이티브 라이브러리 로딩 시 사용되는 mmap 영역.
네이티브 메모리 추적(NMT) 소개
JVM은 -XX:NativeMemoryTracking 옵션을 통해 네이티브 메모리 사용 현황을 실시간으로 감시할 수 있는 기능을 제공한다. 이를 통해 힙 외부에서 발생하는 메모리 누수나 비정상적인 증가를 식별할 수 있으며, 특히 장시간 실행되는 서버 애플리케이션의 안정성 확보에 중요하다.
사용 방법
1. JDK 버전 확인
NMT는 JDK 7u4 이상부터 지원되며, 상세도(detail level)는 JDK 8 이상에서 사용 가능하다. 본 예제는 JDK 17 기준으로 설명한다.
2. 테스트 애플리케이션 작성
지속적으로 메모리를 소비하는 간단한 자바 프로그램을 준비한다:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MemoryGrowthSimulator {
private static final List<byte[]> bufferPool = new ArrayList<>();
public static void simulateWorkload() {
byte[] chunk = new byte[10 * 1024]; // 10KB
new Random().nextBytes(chunk);
bufferPool.add(chunk);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
while (!Thread.interrupted()) {
simulateWorkload();
}
}
}
3. 컴파일 및 실행
NMT 활성화 옵션을 포함하여 애플리케이션을 시작한다:
javac MemoryGrowthSimulator.java
java -XX:NativeMemoryTracking=detail MemoryGrowthSimulator
4. 기준선 설정
애플리케이션 PID를 확인한 후, 초기 상태의 메모리 사용량을 기준선으로 설정한다:
jcmd <pid> VM.native_memory baseline scale=MB
5. 메모리 스냅샷 촬영
일정 시간 후 추가 데이터를 수집하여 변화를 비교한다:
jcmd <pid> VM.native_memory detail scale=MB > nmt_snapshot.txt
6. 출력 결과 분석
결과 파일에는 다음과 유사한 정보가 포함된다:
Total: reserved=3.4GB, committed=210MB
- Java Heap (reserved=2048MB, committed=128MB)
- Class (reserved=1024MB, committed=0MB)
- Thread (reserved=21MB, committed=21MB)
- Code (reserved=240MB, committed=7MB)
- GC (reserved=116MB, committed=45MB)
...
각 항목은 reserved(예약됨)와 committed(실제 할당됨) 값을 가지며, 특정 카테고리에서의 급격한 증가가 문제의 단서가 될 수 있다. 예를 들어 GC 영역의 지속적 증가는 G1GC의 Mark Stack 확장 또는 Region 관리 오버헤드를 시사할 수 있다.
7. 차이 분석
두 시점의 스냅샷을 비교하여 메모리 증가 원인을 파악할 수 있다:
jcmd <pid> VM.native_memory summary.diff scale=MB
이 명령은 이전 baseline 이후의 증감량만 표시하여 변화 추이를 쉽게 파악하게 해준다.
주의 사항
- NMT는 자체적으로 약간의 오버헤드를 유발하므로, 프로덕션 환경에서는 필요 시에만 활성화해야 한다.
detail모드는 최대 정밀도를 제공하지만,summary모드보다 더 많은 메모리를 소비한다.- 메모리 누수 의심 시, 여러 시점의 스냅샷을 반복 촬영하여 증가 패턴을 분석하는 것이 효과적이다.