자바 언어·플랫폼 용어 사전
JVM힙 · 스택 · 메타스페이스 · 런타임 데이터 영역

JVM 메모리 구조

스레드가 공유하는 힙·메타스페이스와 스레드마다 갖는 스택·PC로 나뉜다. OOM 메시지가 어디를 가리키는지 알려 준다.

JVM이 관리하는 메모리는 공유하는 곳스레드마다 갖는 곳으로 나뉜다.

[모든 스레드가 공유]
  힙(Heap)         객체와 배열. GC 의 대상
  메타스페이스      클래스 메타데이터 (Java 8+ · 네이티브 메모리, 기본 무제한)
[스레드마다 하나씩]
  스택             메서드 호출마다 프레임을 쌓는다 (지역 변수 · 부분 결과)
  PC 레지스터       지금 실행 중인 바이트코드 주소
  네이티브 스택     JNI 호출용

객체 하나가 놓이는 모습

void f() {
    Member m = new Member("철수", 20);
}
[스택 — f() 의 프레임]        [힙]
  m │ 0x7A3 │ ─────────────→ ┌────────────────────────┐
                             │ 헤더 (mark word · klass)│
                             │ name → 0x9C1            │
                             │ age  = 20               │
                             └─────────────────────────┘
                                       ↓
                             ┌────────────────────────┐
                             │ String "철수"          │
                             └────────────────────────┘

참조 변수는 스택, 객체 실체는 힙이라는 게 기본 그림이다.

OOM 메시지가 어디를 가리키나

java.lang.OutOfMemoryError: Java heap space
  → 힙 부족. 누수이거나 -Xmx 가 작다

java.lang.OutOfMemoryError: Metaspace
  → 클래스가 계속 로드된다 (동적 프록시 · 리플렉션 · 잦은 재배포)

java.lang.OutOfMemoryError: unable to create new native thread
  → 힙과 무관. OS 스레드 한계다 (스레드 풀 상한이 없는 경우)

java.lang.StackOverflowError
  → 스택 부족. 재귀가 너무 깊다 (-Xss 로 조정 가능하나 대개 코드 문제)

java.lang.OutOfMemoryError: Direct buffer memory
  → 힙 밖(오프힙). DirectByteBuffer 누수

메시지 뒷부분이 어느 영역인지 말해 준다 — 무작정 -Xmx를 올리기 전에 이걸 먼저 읽는다.

Java 8에서 바뀐 것

  • ~7 — PermGen — 힙 안. 크기 고정 → "PermGen space" OOM 이 흔했다
  • 8~ — Metaspace — 네이티브 메모리. 기본 무제한이라 잘 안 터진다
    • 대신 방치하면 OS 메모리를 다 먹는다 → -XX:MaxMetaspaceSize 로 상한을 둔다

확인하는 명령

jcmd <pid> VM.native_memory summary     # 영역별 사용량 (NMT 켜야 함)
jmap -histo <pid> | head -20            # 어떤 클래스가 메모리를 먹나
jcmd <pid> GC.heap_info
jstack <pid>                            # 스레드 수 · 데드락

문자열 상수 풀

String a = "hi", b = "hi";
a == b;                      // true — 상수 풀에서 공유된다
new String("hi") == a;       // false — 힙에 새 객체
"hi".intern();               // 풀의 인스턴스를 얻는다

풀은 Java 7부터 힙에 있다(그전엔 PermGen). 그래서 GC 대상이 된다.

면접 함정

  • "원시 타입은 항상 스택" → 객체의 필드면 힙에 있다.
  • "OOM은 전부 힙 문제" → 메타스페이스·네이티브 스레드·다이렉트 버퍼는 힙과 무관하다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JVM 실행 흐름 — 내 코드가 도는 과정