언어/java

객체의 메모리 레이아웃과 Heap(JVM)

sorecord 2026. 1. 23. 20:03

너무 많은 양의 자료가 오히려 혼란스럽게 한다. 

객체의 메모리 레이아웃은 만들어지는 객체의 구조라고 보면된다. 해당 구조의 형태로 Heap이라는 곳에 저장이 된다. Heap은 이전 글에서도 여러번 적었듯 아래와 같은 형태이다.

Heap 메모리 구조.

GC가 발생하는 과정을 시각화한 것이라 착각했었다. 위의 이미지가 Heap 메모리 구조이고 객체가 만들어졌을때 객체의 메모리 구조의 형태로 Eden이라는 곳에 들어가게 되는 것이다 

 


객체의 메모리 레이아웃( = 객체의 구조)

 

 

위 두 이미지 같은 것이니 이해하기 편한 걸 보면 된다.

 

객체라는 것은 단순히 인스턴스 데이터의 집합만이 아니다. 여러 정보들이 모여서 하나의 객체를 이루고 있는 것이다.


Mark Word(마크워드)

  • Markword는 해시 코드(hash code), 락 플래그(lock flags), GC 상태 정보, 스레드의 잠금 및 언락 정보 등을 포함
  • 객체 자체에 존재하며, JVM의 효율적인 동시성 및 GC 구현에 필수적

포인터가 아니라 그 자체로 데이터를 담고 있는 비트 필드(bit field)로, 각 객체 별로 고유하게 관리되는 메타데이터가 저장된다. 대표적으로 해시 코드는 객체의 메묄 주소를 가리키어 각 객체 별로 고유하게 할당되기에, 마크 워드에 할당된다. 그 외에도 Generational GC(세대별 GC)를 위한 나이 및 객체가 GC의 대상으로 수집되었는지 여부를 표시하는 마킹 비트 등이 존재한다.

 

들어있는 정보들.

  • Lock 정보: 경량(lock-free), 모니터 락(Heavyweight Lock) 등 동기화 상태
  • GC Age: GC 구현을 위한 객체의 나이
  • HashCode: 객체의 해시코드 값으로, hashCode()가 처음 호출될 때 저장됨
  • Unused: 미사용 구간
  • Lock정보(쓰레드 동기화를 위해 필요함)

마크 워드에는 위의 정보들이 순차적으로 비트 단위로 인코딩되어 저장된다. 참고로 위의 설명에도 적혀있듯이, HashCode는 객체의 생성 초기에는 0으로 존재하다가, hashCode()가 처음 호출될 때 할당된다.

 

@쓰레드 동기화 : 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는것

락에 관한 스레드 동기화 내용은 다른 글에 포스팅 하겠다.

 


Klass word(클래스 워드)

포인터이다. 해당 객체의 클래스 메타데이터가 존재하는 메모리 주소를 가리킨다. 클래스에 종속적이라 모든 객체마다 동일한 메타데이터는 하나로 관리하면 되기 때문에, 이를 클래스 공유 메타데이터라고도 한다. 대표적으로 클래스 정보(klass)가 클래스 워드를 통해 얻어와지는 정보 중 하나로, 해당 객체가 어떤 클래스의 인스턴스인지를 표현한다.

 

들어있는 정보

  • 클래스 정보 (InstanceKlass 구조체 주소): 해당 객체가 어떤 클래스의 인스턴스인지 나타냄
  • VTable (Virtual Method Table) 포인터: 가상 메서드 테이블 주소 (다형성을 지원하기 위한 메서드 테이블)
  • 필드 정보: 필드의 오프셋, 타입 정보
  • 정적 변수(Static Fields): 해당 클래스의 정적 필드에 대한 정보

 

자바는 개발자들을 위해 클래스 정보를 표현하는 Class 타입을 제공하여, 클래스에 대한 원하는 정보를 획득하고 객체를 생성할 수 있는 등의 기능을 제공한다. 하지만 JVM 내부적으로 관리하는 Klass는 개발자들에게 친숙한 Class와는 다른 개념이다.

JVM은 내부적으로 빌드를 하고 클래스를 로딩할 때, 클래스에 대한 메타데이터가 필요한데, 클래스 파일에서 뽑아내는 클래스의 정보가 바로 Klass인 것이다. JVM은 얻어낸 Klass 정보를 바탕으로 자바를 위한 복제본 혹은 미러(mirror)를 생성하는데, 이것이 바로 Class 객체로 자바에 반환되는 것이다.

klass word는 모든 클래스가 공유하는 메타데이터 정보이기 때문에 JVM Metaspace라는 별도의 영역에서 저장 및 관리된다.

 

@Metaspace (= Native Area, Native Memory, Non-heap, Direct Memory)

Metaspace는 간단히 말해 Java의 Classloader가 로드한 class들의 metadata가 저장되는 공간. 즉,Metaspace가 Method Area를 포함합니다. 즉, 클래스 메타데이터, 메서드 정보 등이 Metaspace에 저장됩니다.

Metaspace는 기본적으로 운영 체제 메모리를 사용하기 때문에, 전통적인 GC에 의해 자동으로 관리되지 않는다. 

@metadata - jvm process안에 있는 java class들의 runtime으로, jvm이 해당 class에 알아야 하는 모든 정보이다. 

Metadata의 예를 들면, java.lang.Class는 Java heap안에 사는 object다. 하지만 그(= java.lang.Class) class metadata는 Java object가 아니며 heap 안에 위치하지 않는다.. 이 metadata는 heap 외부의 native memory 구역에 위치하게 되었으며, 바로 이 구역이 Metaspace인 것입니다.

 


인스턴스 데이터와 정렬 패딩

 

객체 레이아웃의 두 번째 부분인 인스턴스 데이터는 객체가 실제로 담고 있는 정보다. 예컨대 프로그램 코드에서 정의한 다양한 타입의 필드 관련 내용, 부모 클래스 유무, 부모 클래스에서 정의한 모든 필드가 이 부분에 기록된다. +XX:ComactFields 매개 변수를 true로 설정하면(기본값이 true임) 하위 클래스의 필드 중 길이가 짧은 것들은 상위 클래스의 변수 사이사이에 끼워 넣어져서 공간이 조금이나마 절약된다.

정보의 저장 순서는 JVM의 할당 전략 매개 변수(-XX:FieldsAllocationStyle)와 자바 소스 코드에서 필드 선언 순서에 따라 달라진다.

long, int, short, byte등 다양한 타입이 있고, 길이(크기)가 같은 필드들은 항상 같이 할당되고 저장된다.

 

정렬 패딩 부분은 존재하지 않을 수도 있으며, 특별한 의미 없이 자리를 확보하는 역할만 한다. 핫스팟 가상 머신의 자동 메모리 관리 시스템에서 객체의 시작 주소는 반드시 8바이트 정수배여야 한다. 달리 말하면 모든 객체의 크기가 8바이트의 정수배여야 한다는 뜻이다. 따라서 인스턴스 데이터가 조건을 충족하지 못하는 경우에만 패딩으로 채운다.

 

 


 

이러한 객체의 헤더 부분이 메모리 상에서 어떻게 구성되는지를 쉽게 표현하면 다음과 같다. 마크 워드는 메타데이터로 힙 내부에 존재하지만, 클래스 워드는 클래스 정보를 가리키는 포인터이기에 최종적으로 메타스페이스 영역을 향한다.

 

 

 


객체에 접근하기

대다수의 객체는 다른 객체 여러 개를 참조하여 만들어진다. JVM마다 객체 내 참조 객체에 접근하는 방법이 다르며, 주로 핸들이나 다이렉트 포인터를 사용한다.

 

핸들 방식

자바 힙에 핸들 저장용 풀이 별도로 존재하며, 참조에는 객체의 핸들 주소가 저장되고 핸들에는 다시 해당 객체의 인스턴스 데이터, 타입데이터, 구조 등의 정확한 주소 정보가 담길 것이다.

이런 방식은 안정적이다. GC중 객체의 위치가 바뀌는 상황에서도 참조 자체에는 손댈 필요가 없다. 그 대신 핸들내의 인스턴스 데이터 포인터만 변경하면 된다.

 

다이렉트 포인터 방식

 

다이렉트 포인트 방식에서는 자바 힙에 위치한 객체에서 인스턴스 데이터뿐 아니라 타입 데이터에 접근하는 길도 제공해야 한다. (스택의 참조에는 객체의 실제 주소가 바로 저장되어 있다.) 따라서 빠르다.

https://www.artima.com/insidejvm/ed2/jvm6.html

 

Java Virtual Machine's Internal Architecture

Chapter 5 of Inside the Java Virtual Machine The Java Virtual Machine by Bill Venners <<  Page 6 of 13  >> Advertisement The Heap Whenever a class instance or array is created in a running Java application, the memory for the new object is allocated fr

www.artima.com

더 자세한 내용 참고.


배열의 길이

자바 배열의 경우 배열 길이도 객체 헤더에 저장한다. 객체 헤더에 저장되는 객체 타입은 배열에 담긴 ‘원소’의 타입에 해당한다. 따라서 배열 길이까지 알아야 배열 객체가 차지하는 메모리 크기를 제대로 계산할 수 있다. 배열의 길이 정보는 해당 객체가 배열이 아니라면 존재하지 않는다. 따라서 객체 헤더의 길이는 배열 유무에 따라 달라질 수 있다.

 

 

 


Heap에 대해 설명하기 위해선 garbage collection에 대해 알아야 한다. 


GC란?

자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스를 말한다.

 

 

C / C++ 언어에서는 이러한 가비지 컬렉션이 없어 프로그래머가 수동으로 메모리 할당과 해제를 일일이 해줘야 했었다.

반면 Java에서는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 Java 프로세스가 한정된 메모리를 효율적으로 사용할수 있게 하고, 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 관리하지 않아도 되어 오롯이 개발에만 집중할 수 있다는 장점이 있다.

 

문제점도 존재한다. 

자동으로 처리해준다 해도 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들며, 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다.

이를 전문적인 용어로 Stop-The-World 라 한다.

 

STW (Stop The World)
GC를 수행하기 위해 JVM이 프로그램 실행을 멈추는 현상을 의미.
GC가 Major GC를 진행할 때, JVM이 모든 APP의 실행을 멈춰버린다.
GC가 작동하는 동안 GC 관련 Thread를 제외한 모든 Thread는 멈추게 되어 서비스 이용에 차질이 생길 수 있다.
따라서 이 시간을 최소화 시키는 것이 쟁점이다.

 

이로 인해 GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 하다.

예를들면 익스플로러는 이 가비지 컬렉션를 너무 자주 실행하여 성능 문제를 일으키는 것으로 악명이 높았다.

이런 특성에 따라 실시간 성이 매우 강조되는 포로그램일 경우 가비지 컬렉터(GC)에게 메모리를 맞기는 것은 맞지 않을 수 있다.

따라서 어플리케이션의 사용성을 유지하면서 효율적이게 GC를 실행하는 최적화 작업이 개발자의 숙제가 된다.

그리고 이러한 GC 최적화 작업을 GC 튜닝이라고 한다.


 

가비지 컬렉션 대상

그럼 가비지 컬렉션(Garbage Collection)은 어떤 Object를 Garbage로 판단해서 스스로 지워버릴까?

가비지 컬렉션은 특정 객체가 garbage인지 아닌지 판단하기 위해서 도달성, 도달능력(Reachability) 이라는 개념을 적용한다.

 

객체에 레퍼런스가 있다면 Reachable로 구분되고, 객체에 유효한 레퍼런스가 없다면 Unreachable로 구분해버리고 수거해버린다. 

  • Reachable : 객체가 참조되고 있는 상태
  • Unreachable  : 객체가 참조되고 있지 않은 상태 (GC의 대상이 됨) 

 

 

예를들어 JVM 메모리에서는 객체들은 실질적으로 Heap영역에서 생성되고 Method Area나 Stack Area 에서는 Heap Area에 생성된 객체의 주소만 참조하는 형식으로 구성된다.

하지만 이렇게 생성된 Heap Area의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 Heap Area 객체의 메모리 주소를 가지고 있는 참조 변수가 삭제되는 현상이 발생하면, 위의 그림에서의 빨간색 객체와 같이 Heap영역에서 어디서든 참조하고 있지 않은 객체(Unreachable)들이 발생하게 된다.

이러한 객체들을 주기적으로 가비지 컬렉터가 제거해주는 것이다.


 

가비지 컬렉션 청소 방식

GC가 Unreachable한 객체를 어떤 방식으로 청소를 하는지 알아보자.

Mark And Sweep

Mark-Sweep 이란 다양한 GC에서 사용되는 객체를 솎아내는 내부 알고리즘이다.

가비지 컬렉션이 동작하는 아주 기초적인 청소 과정이라고 생각하면 된다.

 

 

원리는 간단하다.

가비지 컬렉션이 될 대상 객체를 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행하게 된다.

  • Mark 과정 : 먼저 Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
  • Sweep 과정 : 참조하고 있지 않은 객체 즉 Unreachable 객체들을 Heap에서 제거한다.
  • Compact 과정 : Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다. (가비지 컬렉터 종류에 따라 하지 않는 경우도 있음)

 

 

이렇게 Mark And Sweep 방식을 사용하면 루트로부터 연결이 끊긴 순환 참조되는 객체들을 모두 지울수 있다.

[ GC의 Root Space ]

Mark And Sweep 방식은 루트로 부터 해당 객체에 접근이 가능한지가 해제의 기준이 된다.
JVM GC에서의 Root Space는 Heap 메모리 영역을 참조하는 method area, static 변수, stack, native method stack이 되게 된다.

 

 


GC 동작 과정

 

heap 메모리의 구조

JVM의 힙(heap) 영역은 동적으로 레퍼런스 데이터가 저장되는 공간으로서, 가비지 컬렉션에 대상이 되는 공간이다.

Heap영역은 처음 설계될 때 다음의 2가지를 전제 (Weak Generational Hypothesis)로 설계되었다.

  • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
  • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.

즉, 객체는 대부분 일회성되며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다.

이러한 특성을 이용해 JVM 개발자들은 보다 효율적인 메모리 관리를 위해, 객체의 생존 기간에 따라 물리적인 Heap 영역을 나누게 되었고 Young 과 Old 총 2가지 영역으로 설계하였다.

 

 

 

Young 영역(Young Generation)

  • 새롭게 생성된 객체가 할당(Allocation)되는 영역
  • 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
  • Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부른다.

 

Old 영역(Old Generation)

  • Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
  • Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
  • Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC 또는 Full GC라고 부른다.

GC 동작과정 중 Minor GC과정

 

Young Generation 영역은 짧게 살아남는 메모리들이 존재하는 공간이다.

모든 객체는 처음에는 Young Generation에 생성되게 된다.

Young Generation의 공간은 Old Generation에 비해 상대적으로 작기 때문에 메모리 상의 객체를 찾아 제거하는데 적은 시간이 걸린다. (작은 공간에서 데이터를 찾으니까)

이 때문에 Young Generation 영역에서 발생되는 GC를 Minor GC라 불린다.


1. 처음 생성된 객체는 Young Generation 영역의 일부인 Eden 영역에 위치

 

 

 

2. 객체가 계속 생성되어 Eden 영역이 꽉차게 되고 Minor GC가 실행

 

 

 

3. Mark 동작을 통해 reachable 객체를 탐색

 

 

 

4. Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동

 

 

 

5. Eden 영역에서 사용되지 않는 객체(unreachable)의 메모리를 해제(sweep)

 

 

 

6. 살아남은 모든 객체들은 age값이 1씩 증가

 

 

Info

[ age 값이란? ]
Survivor 영역에서 객체가 살아남은 횟수를 의미하는 값이며, Object Header(위에서 말한 mark word)에 기록된다.
만일 age 값이 임계값에 다다르면 Promotion(Old 영역으로 이동) 여부를 결정한다.
JVM 중 가장 일반적인 HotSpot JVM의 경우 이 age의 기본 임계값은 31이다.
객체 헤더에 age를 기록하는 부분이 6 bit로 되어 있기 때문이다.

또한 Survivor 영역의 제한 조건으로 Survivor 영역 중 반드시 1개는 사용되어야 하고, 나머지는 비어 있어야 한다.
만약 두 Survivor 영역에 모두 데이터가 존재하거나, 모두 사용량이 0이라면 현재 시스템이 정상적인 상황이 아니라는 반증이 된다.

 

7. 또다시 Eden 영역에 신규 객체들로 가득 차게 되면 다시한번 minor GC 발생하고 mark 한다

 

 

 

8. marking 한 객체들을 비어있는 Survival 1으로 이동하고 sweep

 

 

 

10. 다시 살아남은 모든 객체들은 age가 1씩 증가

 

 

 

11. 이러한 과정을 반복

 

 


GC 과정 중 Major GC과정

 

Old Generation은 길게 살아남는 메모리들이 존재하는 공간이다.

Old Generation의 객체들은 거슬러 올라가면 처음에는 Young Generation에 의해 시작되었으나, GC 과정 중에 제거되지 않은 경우 age 임계값이 차게되어 이동된 녀석들이다.

그리고 Major GC는 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생하게 된다.

Tip

Major GC는 Full GC라고도 불리운다.

Minor GC와 Major GC 차이점을 표로 정리하면 다음과 같이 된다.

 

 


1. 객체의 age가 임계값(여기선 8로 설정)에 도달하게 되면,

 

 

 

2. 이 객체들은 Old Generation 으로 이동된다. 이를 promotion 이라 부른다.

 

 

 

3. 위의 과정이 반복되어 Old Generation 영역의 공간(메모리)가 부족하게 되면 Major GC가 발생되게 된다.

 

 

 

 

Major GC는 Old 영역은 데이터가 가득 차면 GC를 실행하는 단순한 방식이다. 

Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 Major GC가 실행되게 된다.

하지만 Old Generation은 Young Generation에 비해 상대적으로 큰 공간을 가지고 있어, 이 공간에서 메모리 상의 객체 제거에 많은 시간이 걸리게 된다.

 

예를들어 Young 영역은 일반적으로 Old 영역보다 크키가 작기 때문에 GC가 보통 0.5초에서 1초 사이에 끝난다.

그렇기 때문에 Minor GC는 애플리케이션에 크게 영향을 주지 않는다.

하지만 Old 영역의 Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용한다.

 

바로 여기서 Stop-The-World 문제가 발생하게 된다.

Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 현상이 일어나기 때문이다.

따라서 자바 개발진들은 끊임 없이 가비지 컬렉션 알고리즘을 발전시켰다.

 


 

https://stackoverflow.com/questions/1262328/how-is-the-java-memory-pool-divided/1262474#1262474
https://www.artima.com/insidejvm/ed2/jvm6.html
https://stackoverflow.com/questions/41358895/permgen-is-part-of-heap-or-not
https://mangkyu.tistory.com/448   
https://www.inflearn.com/community/questions/1560054/metaspace%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%88%EB%AC%B8?srsltid=AfmBOopuyh_03NhQk_At9YdpB4WR2WyboWd6-yV4wJTEhnLNcuSgKPbe
https://www.inflearn.com/community/questions/1452848/%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-%EC%9C%84%EC%B9%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4
https://velog.io/@beneficial/%EC%9E%90%EB%B0%94-JVM-Heap
https://velog.io/@cksgodl/JVM-%EB%B0%91%EB%B0%94%EB%8B%A5%EA%B9%8C%EC%A7%80-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-2%EC%9E%A5-%EC%9E%90%EB%8F%99%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B4%80%EB%A6%AC
https://nangmandeveloper.tistory.com/2
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%EC%85%98GC-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC

참고 자료들

 

공부용으로 작성한 글입니다. 문제시 비공개 처리하겠습니다. 

'언어 > java' 카테고리의 다른 글

Runtime Data Areas(JVM)이란?  (0) 2026.01.21
JVM의 Class Loader(클래스 로더)란?  (0) 2026.01.19
JVM이란? 메모리 구조!  (0) 2026.01.14