JVM new와 Eden: 객체가 처음 할당되는 곳
TOC
Overview
앞 글인 Java 객체 생성과 메모리 배치에서는 new를 만나면 인스턴스가 heap에 만들어지고, 참조는 stack/local variable에 저장된다는 흐름을 다뤘다.
그다음 질문은 이것이다.
new로 만들어진 객체는 heap 안에서도 어디에서 처음 자리를 잡을까?
heap의 큰 구조는 JVM heap 구조와 객체 배치에서 먼저 정리했다. 이번 글에서는 그중에서도 새 객체가 처음 들어가는 Eden에 대해 다룬다.
Eden이란?
Eden은 Young Generation 안에서 새 객체가 처음 자리를 잡는 영역이다.
flowchart TB
Start["new"]
subgraph Heap["Heap"]
direction TB
subgraph Young["Young Generation"]
direction LR
Eden["Eden"]
S0["Survivor 0"]
S1["Survivor 1"]
end
subgraph OldGen["Old Generation"]
direction TB
Old["Old / Tenured"]
end
end
Start -->|new| Eden
Eden -->|Minor GC 후 생존| S0
S0 -->|Aging| S1
S1 -->|Promotion| Old
class Eden edenStyle
classDef edenStyle fill:#fff3cd,stroke:#f59e0b,stroke-width:4px,color:#111,font-weight:bold
일반적인 경우 새 객체는 Young Generation 안에서도 Eden 영역에서부터 시작된다.
그래서 Eden은 이 글에서 새 객체의 시작점이자 빠른 할당 구역으로 정의 할 수 있다.
new와 Eden의 동작 개념
new가 실행되면 JVM은 heap 내부의 Eden에 객체를 만들 공간을 확보해야 한다.
이때 기본 흐름은 아래처럼 생각할 수 있다.
new Object()
-> 객체 크기 계산
-> Young Generation의 Eden 확인
-> Eden에 공간이 있으면 객체 배치
중요한 점은 Eden이 새 객체를 받기 위한 구역이라는 것이다.
대부분의 객체는 오래 살지 않는다. 메서드 안에서 잠깐 만들어 쓰고 버려지는 객체, 요청 하나를 처리하는 동안만 필요한 DTO, 반복문 안에서 생겼다가 바로 사라지는 임시 객체가 많다.
이런 객체를 처음부터 오래 사는 객체와 섞어 관리하면 비용이 커진다. 그래서 JVM은 새 객체를 Eden에 모아두고, 이후 GC 과정에서 살아남은 객체만 다음 영역으로 이동시킨다.
Eden에 실제로 어떤 방식으로 공간이 잡히는지는 TLAB: thread-local allocation buffer에서 이어서 본다. Eden을 정리하는 역할과 살아 있는 객체를 Survivor로 옮기는 흐름은 JVM Minor GC: Eden을 비우고 살아남은 객체를 옮기는 과정에서 이어서 본다.
bump pointer
bump == 밀어내다
bump pointer 란 포인터를 미는 작업을 의미한다. (== “자원 할당 기법” 으로도 표현이 가능하다.)
보통 객체를 할당할 때 매번 빈 공간을 탐색해서 할당하는 방식은 “탐색한다” 라는 오버헤드 비용이 존재한다.
할당하고자하는 공간이 정리된 연속 공간이라면, 그리고 할당 가능한 부분에 표시하는방식을 이용하면 굳이 “탐색”하는 비용을 아낄 수 있다.
현재까지 사용한 위치를 가리키는 포인터를 하나 두고, 객체 크기만큼 앞으로 밀면 된다.
이 방식을 흔히 bump pointer allocation이라고 한다.
bump pointer는 연속된 빈 공간에서 다음 할당 위치를 가리키는 포인터를 객체 크기만큼 앞으로 밀어 객체를 배치하는 방식이다.
객체를 할당할 때마다 heap 전체에서 빈 공간을 탐색하면 비용이 크다. 반대로 Eden 안의 사용 가능한 공간이 연속적으로 정리되어 있다면, JVM은 현재 할당 가능한 위치만 기억하고 있으면 된다.
예를 들어 현재 할당 포인터가 1000번 주소를 가리키고 있고, 새 객체 크기가 40 byte라면 객체를 1000부터 배치한 뒤 포인터를 1040으로 옮긴다.
Eden 다음에는 어디로 가는가
Eden에 들어간 객체가 모두 오래 살아남는 것은 아니다.
참조가 끊긴 객체는 이후 GC 대상이 된다. 반대로 아직 참조되고 있는 객체는 Minor GC 과정에서 Survivor 영역으로 이동할 수 있다. 더 오래 살아남은 객체는 나중에 Old Generation으로 승격될 수 있다.
Conclusion
new는 객체를 heap 안에서 Eden 에 만든다.
- 새 객체는 보통
Young Generation의Eden에서부터 시작된다. Eden은 새 객체를 모아두는 시작점이자 빠른 할당 구역이다.bump pointer는 현재 할당 위치를 객체 크기만큼 앞으로 밀어 객체를 배치하는 방식이다.Eden에서 살아남은 객체는 이후Survivor로 이동할 수 있다.
더 자세한 thread-local allocation 흐름은 TLAB: thread-local allocation buffer에서, Minor GC에 의한 생존과 이동은 jvm young generation에서 이어서 본다.
Next Step
다음 글에서는 TLAB(Thread-Local Allocation Buffer)을 본다.
핵심 질문은 이것이다.
여러 thread가 동시에 객체를 만들 때, JVM은 어떻게 할당 경합을 줄일까?