이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • JVM Memory 영역과 Metaspace에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • heap 밖 JVM memory도 운영 memory다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 JVM Memory 영역과 Metaspace 판단 기준은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 JVM Memory 영역과 Metaspace이다. Heap, metaspace, code cache, thread stack, direct memory를 구분해 관찰하는 방법을 정리한다.

먼저 붙잡을 기준은 다음 문장이다. heap 밖 JVM memory도 운영 memory다. -Xmx는 heap 상한일 뿐이고, 실제 process RSS에는 metaspace, code cache, thread stack, direct buffer, native allocation이 함께 들어간다.

핵심 모델

  • 판단 기준: heap 밖 JVM memory도 운영 memory다.
  • JVM memory는 heap, metaspace, code cache, thread stack, direct/native memory로 나뉜다.
  • Heap used가 안정적이어도 RSS가 증가하면 heap 밖 memory를 의심해야 한다.
  • Metaspace는 class metadata를 저장하며 classloader leak, proxy, dynamic class 생성과 연결된다.
  • Thread 수가 늘면 thread stack memory도 함께 늘어난다.
  • Native Memory Tracking은 heap 밖 memory를 범주별로 나누어 보는 데 유용하지만 사전 option과 overhead를 고려해야 한다.

문서별 핵심 구분

  • heap 밖에도 metaspace, code cache, direct memory, thread stack이 있다.
  • Metaspace 증가는 classloader leak이나 동적 class 생성을 의심하게 한다.
  • NMT는 heap 밖 memory를 나눠 보는 데 유용하다.

원리

Heap은 Java 객체가 주로 머무는 영역이고 GC의 직접 관리 대상이다. 그러나 운영에서 보이는 RSS는 heap만의 크기가 아니다.

Metaspace는 class metadata를 저장한다. Spring proxy, reflection, JPA entity, classloader가 많은 환경에서는 metaspace 사용량이 커질 수 있고, classloader leak이 있으면 GC 후에도 내려오지 않는다.

Code cache는 JIT가 만든 native code를 저장하고, thread stack은 thread마다 별도로 잡힌다. Direct buffer와 JNI/native allocation도 heap 밖에 잡힌다.

따라서 memory 장애를 볼 때는 jvm.memory.used만 보지 말고 heap, non-heap, RSS, thread 수, direct memory, metaspace를 같은 시간축으로 비교해야 한다.

Java/Spring 연결

  • Spring AOP proxy, JPA, validation, JSON library는 class metadata와 reflection 사용량에 영향을 줄 수 있다.
  • Actuator jvm.memory.used는 heap/non-heap tag를 나눠 볼 수 있지만 RSS와 직접 같지 않다.
  • WebClient/Netty나 NIO 기반 라이브러리는 direct buffer를 사용해 heap 밖 memory를 늘릴 수 있다.
  • Tomcat worker thread, scheduler, async executor가 늘면 thread stack memory도 늘어난다.
  • Container에서는 -Xmx 외 metaspace, stack, direct memory, OS page cache 여유를 함께 남겨야 한다.

코드와 설정 예시

jcmd <pid> GC.heap_info
jcmd <pid> VM.metaspace
jcmd <pid> VM.native_memory summary
cat /proc/<pid>/status | grep -E 'VmRSS|Threads'

예시는 heap, metaspace, native memory, RSS/thread 수를 한 번에 맞춰 보는 흐름이다. NMT가 꺼져 있으면 VM.native_memory가 제한될 수 있으므로 사전 option과 운영 overhead를 확인해야 한다.

관찰 명령

jcmd <pid> GC.heap_info
jcmd <pid> VM.native_memory summary
jcmd <pid> VM.metaspace

JVM Memory 영역과 Metaspace 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.

지표 해석

신호먼저 의심할 것다음 확인
Heap used 안정, RSS 증가direct/native/thread stack 증가NMT, /proc/<pid>/status
Metaspace 지속 증가classloader leak, dynamic classVM.metaspace, classloader stats
Thread 수 증가stack memory 증가thread dump, executor metric
Code cache 경고JIT code cache 압박Compiler.CodeHeap_Analytics

Heap, non-heap, RSS, thread 수의 변화를 한 타임라인에 놓아야 heap leak, metaspace 누적, direct/native 증가, stack memory 증가를 서로 구분할 수 있다.

장애 상황에서 보는 순서

  1. Heap used, non-heap, RSS를 같은 시각에 확인한다.
  2. RSS가 높으면 metaspace, direct/native, thread stack 중 어디가 늘었는지 나눈다.
  3. Thread 수, direct buffer 사용 라이브러리, classloader 수를 확인한다.
  4. NMT가 켜져 있으면 summary/detail을 비교하고, 꺼져 있으면 다음 재현 환경에서 켤지 판단한다.
  5. -Xmx 증설 전 container limit과 heap 밖 여유를 계산한다.

이 순서는 heap 밖 memory를 빼먹지 않기 위한 루틴이다. JVM 내부 지표와 OS RSS가 맞지 않으면 heap dump 하나로 결론을 내리지 않는다.

실전 팁

  • -Xmx와 process RSS를 같은 값으로 보지 않는다.
  • Heap dump로 설명되지 않는 RSS 증가는 direct, stack, metaspace, native를 순서대로 본다.
  • Metaspace 문제는 class count와 classloader 수를 함께 봐야 한다.
  • Thread 수가 급증하면 heap보다 stack memory와 scheduler 비용을 먼저 의심한다.
  • 장애 회고에는 heap/non-heap/RSS/container limit을 표로 남긴다.

주니어 팁

  • Heap은 JVM memory의 전부가 아니라는 점을 먼저 기억한다.
  • RSS가 -Xmx보다 큰 것은 이상이 아니라 heap 밖 영역이 포함되기 때문이다.
  • Metaspace는 class metadata와 classloader 문제를 볼 때 사용한다.

시니어 팁

  • Container memory limit은 heap뿐 아니라 metaspace, direct, stack, page cache까지 합산해 산정한다.
  • NMT는 운영 기본값으로 켤지, 장애 재현 환경에서만 켤지 overhead와 함께 결정한다.
  • Thread pool 증설은 stack memory와 native memory 여유를 함께 검토한다.

Guru급 팁

  • Memory 예산은 heap + metaspace + direct + stack + code cache + page cache 항목으로 나눠 잡는다.
  • NMT summary를 정기 기준선으로 남기면 배포 후 native/direct/metaspace 증가가 어느 범주에서 시작됐는지 비교하기 쉽다.
  • Classloader leak은 heap dump보다 classloader 수, unloaded class 추세, metaspace 회수 여부가 더 빠른 단서가 될 수 있다.

위험 신호!

  • Heap used만 보고 memory 문제가 없다고 판단한다.
  • RSS 증가를 모두 heap leak으로 단정한다.
  • Thread 수 증가가 stack memory를 늘린다는 점을 빼고 계산한다.
  • Metaspace 증가를 classloader/class count와 연결하지 않는다.
  • Container limit 안에서 heap 밖 여유를 남기지 않는다.

확인 질문

확인 질문

  • Heap used는 낮은데 RSS가 높을 때 어떤 영역을 의심해야 하는가?
    • Metaspace, direct/native memory, thread stack, code cache, mmap 같은 heap 밖 영역을 의심해야 한다.
  • Metaspace 증가를 판단할 때 함께 봐야 하는 증거는 무엇인가?
    • Class count, classloader 수, dynamic proxy/class 생성, 배포 후 증가 추세를 함께 봐야 한다.
  • -Xmx를 키우기 전에 container 환경에서 확인할 것은 무엇인가?
    • Heap 밖 memory와 OS page cache까지 포함해 container limit 안에 여유가 남는지 확인해야 한다.

참고 문서