HotSpot JVM에서 객체가 차지하는 메모리 구조 분석

객체가 메모리에 그려지는 방식

new 키워드로 생성한 인스턴스는 힙 영역에 할당된다. 그렇다면 실제로 이 인스턴스는 어떤 형태로 누워 있을까? 다음 질문에 답할 수 있어야 JVM 내부를 온전히 이해했다고 볼 수 있다.

  • 인스턴스의 메모리 형태는 어떻게 생겼는가?
  • 아무 필드도 없는 Object 하나가 몇 바이트나 먹는가?
  • 필드들은 메모리 상에서 어떤 순서로 배치되는가?

객체의 세 영역

HotSpot VM은 힙에 올라간 객체를 다음 세 구역으로 나누어 저장한다.

  1. Header(헤더) – 락 상태, GC 정보, 해시코드 등 메타데이터
  2. Fields(필드 데이터) – 클래스에 선언된 인스턴스 변수 값
  3. Padding(패딩) – 8바이트 정렬을 맞추기 위한 빈 공간

Header 세부 구조

Header는 다시 두 워드(word)로 나뉜다.

  • Mark Word – 객체의 런타임 상태 비트맵
  • Klass Pointer – 클래스 메타데이터를 가리키는 포인터

만약 대상이 배열이라면 배열 길이를 저장하는 추가 워드가 붙는다.

Mark Word 비트 분포

32비트 VM 기준(64비트는 64비트로 확장) Mark Word는 다음처럼 쪼개진다.

비트 구간의미
25~56identity hash code (지연 계산)
4~7age (Minor GC 생존 횟수, 최대 15)
3biased_lock 플래그
2~0lock 상태 (001=무/偏向, 00=경량, 10=중량, 11=GC 마크)

age 필드가 4비트라서 15번까지만 카운트할 수 있으며, 이게 객체가 Old 영역으로 승격되는 기본 임계값(15)이 된다.

Klass Pointer

압축 OOP(Compressed Oops) 활성 시 32비트, 비활성 시 64비트로 저장된다. 이 포인터를 통해 VM은 "이 객체가 어떤 클래스의 인스턴스인지"를 알아낸다.

필드 데이터 배치 규칙

HotSpot은 다음 규칙으로 필드를 재정렬해 공간을 최소화한다.

  1. double/long → int/float → short/char → byte/boolean → 참조 순으로 큰 것부터 배치
  2. 슈퍼클래스 필드가 먼저, 서브클래스 필드가 그 뒤에 온다.
  3. 8바이트 단위로 정렬되도록 중간에 gap을 둔다.

정렬(Padding)의 의미

객체 시작 주소는 8바이트 배수여야 한다. 객체 크기가 8의 배수가 아니면 끝에 빈 공간을 채운다. 이는 CPU 캐시 라인 단위로 읽기/쓰기 비용을 줄이기 위한 목적이다.

JOL로 직접 확인하기

openjdk에서 제공하는 JOL(Java Object Layout) 라이브러리로 실제 메모리를 뜯어보자.

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.17</version>
</dependency>

빈 클래스

class Empty { }

public static void main(String[] args) {
    System.out.println(ClassLayout.parseInstance(new Empty()).toPrintable());
}

출력 예시(압축 OOP ON, 64비트):

 OFFSET  SIZE   TYPE DESCRIPTION
      0     8        (object header: mark)
      8     4        (object header: klass)
     12     4        (object alignment gap)
Instance size: 16 bytes

mark 8바이트 + klass 4바이트 + 패딩 4바이트 = 총 16바이트

압축 OOP 끄기

java -XX:-UseCompressedOops Main

같은 Empty 클래스가 16 → 24바이트로 증가한다(klass가 8바이트로 늘어나기 때문).

배열 인스턴스

int[] arr = {42};
System.out.println(ClassLayout.parseInstance(arr).toPrintable());

출력 예시:

 OFFSET  SIZE   TYPE DESCRIPTION
      0     8        (object header: mark)
      8     4        (object header: klass)
     12     4        (array length)
     16     4    int int[0]
     20     4        (alignment gap)
Instance size: 24 bytes

mark 8 + klass 4 + length 4 + 첫 번째 int 4 + 패딩 4 = 24바이트

정리

객체 하나는 단순한 데이터 덩어리가 아니라 header·fields·padding이라는 세 영역이 조화를 이룬 구조다. 특히 header 내부의 Mark Word는 락과 GC 연령 정보를 담고 있어 성능 튜닝과 동시성 분석의 열쇠가 된다. JOL을 이용해 실제 메모리를 들여다보면 추상적인 JVM 스펙이 눈에 보이는 형태로 다가온다.

태그: HotSpot JVM Object Layout JOL Compressed OOPs Mark Word

8월 11일 14:59에 게시됨