
💡 이 글에서 다루는 내용
- new 키워드가 실행될 때 JVM 내부에서 어떤 과정이 순서대로 일어나는지 살펴봐요.
- 자바 객체가 단순한 필드 값의 묶음이 아니라, 객체 헤더와 패딩까지 포함한 구조라는 점을 이해해요.
- 객체의 실제 메모리 구조를 바탕으로 GC가 어떻게 동작하는지, 메모리 누수가 왜 생기는지 추정하는 감각을 익혀봐요.
이전 글에서는 JVM 메모리 영역의 큰 그림을 살펴봤어요.
메서드 영역에는 클래스 메타데이터가 올라가고, 스택에는 메서드 호출 문맥이 쌓이며, 힙에는 객체와 배열이 생성돼요.
하지만 “객체는 힙에 생성된다”는 설명만으로는 객체를 충분히 이해하기 어려워요.
객체는 힙의 한 칸에 놓인 단순한 데이터 덩어리가 아니에요.
JVM은 객체를 만들 때 먼저 어떤 클래스의 인스턴스인지 확인하고, 필요한 메모리 크기를 계산해요.
이어서 필드를 기본값으로 초기화하고, 이후에는 참조를 통해 객체에 접근할 수 있게 해요.
즉, 우리가 변수에 담는 것은 객체 자체가 아니라 객체를 가리키는 참조(Reference)예요.
이번 글에서는 객체가 생성되는 과정부터 메모리 안에서의 구조, 그리고 참조를 통해 객체에 접근하는 방식까지 차례대로 살펴볼게요.
객체를 만들기 전에 필요한 준비: 클래스 로딩

객체는 new 키워드 한 줄로 생성되지만, JVM은 이 작업을 아무 준비 없이 수행하지 않아요.
객체를 만들려면 먼저 클래스 정보가 준비되어 있어야 해요.
User user = new User();
개발자에게는 단순한 한 줄이지만, JVM이 이 코드를 실행하려면 먼저 User 클래스의 정보(설계도)를 알아야 해요.
필드가 몇 개인지, 부모 클래스가 무엇인지, 객체 크기가 얼마나 되는지 알아야 필요한 메모리를 할당할 수 있기 때문이에요.
이때 중요한 역할을 하는 것이 클래스 로딩(Class Loading)이에요.
컴파일된 .class 파일이 JVM에 로드되면, 이를 바탕으로 메서드 영역에 클래스 메타데이터가 준비돼요.
JVM은 이 정보를 바탕으로 객체를 생성해요.
다시 말해, 힙에 객체를 생성하는 일은 메서드 영역에 클래스 정보가 먼저 준비되어 있어야 시작할 수 있어요.
객체 생성의 5단계

클래스 정보가 준비된 뒤 new가 실행되면, JVM은 객체를 다음 5단계로 생성해요.
1. 클래스가 로드되었는지 확인해요
먼저 해당 클래스가 이미 로드되어 있는지 확인해요.
아직 로드되지 않았다면 클래스 로딩을 수행해 메타데이터를 준비해요.
2. 힙에 메모리를 할당해요
객체 크기를 계산한 뒤, 힙에서 필요한 만큼의 메모리를 확보해요.
멀티스레드 환경에서는 더 빠르고 안전하게 할당하기 위해 TLAB 같은 기법을 사용하기도 해요.
3. 기본값으로 초기화해요
확보한 메모리 영역을 기본값으로 초기화해요.
예를 들어 숫자형은 0, boolean은 false, 참조형은 null로 설정돼요.
이 단계 덕분에 생성자가 실행되기 전에도 객체는 일관된 초기 상태를 유지할 수 있어요.
4. 객체 헤더를 설정해요
객체 앞부분에는 헤더가 들어가요.
여기에는 이 객체가 어떤 클래스의 인스턴스인지, 해시코드는 무엇인지, GC가 관리할 때 필요한 정보나 락 상태 같은 값이 저장돼요.
5. 생성자를 실행해요
마지막으로 개발자가 작성한 생성자가 실행돼요.
이때 필드가 의도한 값으로 초기화되면서 객체가 실제로 사용할 수 있는 상태가 돼요.
자바 객체의 메모리 레이아웃

객체 생성 과정을 살펴봤다면, 이제 힙에 생성된 객체가 실제로 어떤 구조를 가지는지 볼게요.
자바 객체는 크게 세 부분으로 구성돼요.
객체 헤더(Object Header)
객체 헤더는 객체의 맨 앞부분에 위치한 런타임 관리 정보예요.
JVM은 이 영역에 객체를 식별하고 관리하는 데 필요한 값을 저장해요.
- Mark Word에는 해시코드, GC 상태, 락 정보처럼 런타임에 변할 수 있는 값이 들어가요.
- 클래스 메타데이터 포인터는 이 객체가 어떤 클래스의 인스턴스인지 알 수 있도록 클래스 정보를 가리켜요.
- 배열 객체인 경우에는 길이 정보도 헤더에 함께 저장돼요.
인스턴스 데이터(Instance Data)
인스턴스 데이터는 우리가 흔히 객체의 내용이라고 생각하는 영역이에요.
클래스에 선언한 필드 값은 이곳에 저장돼요.
여기서 중요한 점은, 상속 관계가 있다면 부모 클래스의 필드도 이 영역에 함께 포함된다는 점이에요.
class Animal { int age; }
class Dog extends Animal { boolean vaccinated; }
예를 들어 Dog 객체를 생성하면, 메모리에는 vaccinated 필드만 들어가는 것이 아니에요.
부모 클래스 Animal에 선언된 age 필드도 함께 배치돼요.
정렬 패딩(Alignment Padding)
객체의 마지막에는 추가 공간이 붙을 수 있어요.
이 공간에는 별도의 필드 값이 들어가지 않지만, 메모리 정렬을 맞추는 역할을 해요.
컴퓨터는 메모리에 접근할 때 특정 바이트 단위로 정렬되어 있으면 더 효율적으로 동작해요.
그래서 JVM은 객체의 전체 크기를 정렬 단위에 맞추기 위해 끝부분에 여유 공간을 덧붙이기도 해요.
이 공간을 정렬 패딩(Alignment Padding)이라고 해요.
객체가 차지하는 메모리 크기는 필드 크기의 합만으로 결정되지 않아요. 객체 헤더와 패딩까지 포함되기 때문에 실제 크기는 더 커질 수 있어요.
참조(Reference)를 통한 객체 접근 방식
User user = new User();
위 코드에서 user 변수에 들어 있는 것은 User 객체 자체가 아니에요.
변수에는 객체를 가리키는 참조(Reference)만 들어 있고, 실제 객체는 힙에 존재해요.
JVM은 이 참조를 따라 실제 객체에 접근해요.
이 방식은 크게 두 가지로 나눌 수 있어요.
- 핸들(Handle) 방식:
참조 -> 핸들 풀 -> 실제 객체
참조는 중간의 핸들을 가리키고, 핸들이 다시 실제 객체를 가리켜요. 이 방식은 GC로 객체의 위치가 바뀌어도 참조값을 직접 바꾸지 않아도 된다는 장점이 있어요. 대신 한 번 더 거쳐 가야 해서 접근 비용이 늘어나요. - 직접 포인터(Direct Pointer) 방식:
참조 -> 실제 객체
참조가 실제 객체를 바로 가리켜요. 중간 단계가 없어서 접근이 더 빠르며, HotSpot JVM을 비롯한 많은 구현체가 이 방식을 사용해요.
즉, 자바에서 변수가 들고 있는 것은 객체 그 자체가 아니라 객체에 도달하기 위한 참조예요.
그리고 JVM은 이 참조를 해석해 힙에 있는 실제 객체에 접근해요.
객체 메모리 구조를 이해해야 하는 이유
객체의 메모리 구조를 이해하면, 자바 코드가 실제로 어떻게 동작하는지 더 입체적으로 볼 수 있어요.
특히 GC, 메모리 사용량, 객체 설계 방식을 이해하는 데 큰 도움이 돼요.
- GC 동작 원리 이해
GC는 참조를 따라가며 살아 있는 객체를 식별해요. 이 과정에서 객체 헤더의 정보도 함께 활용돼요. 그래서 객체 구조를 알고 있으면, 어떤 객체가 회수 대상이 되는지 더 자연스럽게 이해할 수 있어요. - 메모리 사용량 추정
필드가 많지 않은 객체라도 수백만 개가 쌓이면 힙 사용량이 빠르게 늘어날 수 있어요. 객체 크기는 필드의 합으로만 결정되지 않고, 헤더와 패딩도 함께 포함되기 때문이에요. - 더 효율적인 객체 설계
객체를 너무 잘게 나누거나 불필요하게 감싸는 구조는 메모리 사용량과 접근 비용을 늘릴 수 있어요. 객체의 실제 메모리 구조를 이해하면, 이런 설계를 더 신중하게 판단할 수 있어요.
자바 객체는 new 한 번으로 바로 만들어지는 단순한 데이터 덩어리가 아니에요.
객체는 클래스 로딩, 메모리 할당, 헤더 설정, 정렬 같은 과정을 거쳐 생성되고 관리돼요.
그리고 변수는 객체 자체를 담는 대신, 객체를 가리키는 참조를 통해 이에 접근해요.
지금까지 런타임 데이터 영역의 큰 그림을 살펴보고, 그 안에서 객체가 어떻게 생성되는지 조금 더 자세히 들여다봤어요.
여기서 자연스럽게 다음 질문으로 이어져요.
힙에 객체가 계속 쌓이면 메모리는 언제 한계에 도달할까요?
그리고 OutOfMemoryError는 왜 발생할까요?
다음 글에서는 이 흐름을 이어서, 힙 메모리가 한계에 도달하는 과정과 OOM(OutOfMemoryError)이 발생하는 대표적인 원인을 살펴볼게요!
'Language > Java ☕️' 카테고리의 다른 글
| JVM 메모리 장애 분석: OutOfMemoryError와 StackOverflowError를 제대로 이해하기 (0) | 2026.03.09 |
|---|---|
| 자바 메모리 구조: 힙만으로는 부족한 JVM 런타임 데이터 영역 이해하기 (0) | 2026.03.09 |
| Java static 키워드 완전 가이드 (0) | 2024.05.22 |
| 자바 메모리 구조 이해하기 (0) | 2024.05.22 |
| 자바의 가비지 컬렉션(GC) 한 번에 이해하기 (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆