자바 객체 표현: OOP와 Klass
자바에는 기본 타입(Primitive Type)과 객체 참조 타입(Reference Type) 두 가지 종류의 값만 존재합니다. 특히 객체 참조 타입은 내부적으로 Ordinary Object Pointer(OOP)라는 구조체를 통해 관리됩니다.
1. OOP와 객체 헤더
자바에서 new로 객체를 생성하면 JVM 힙에 객체가 할당됩니다. 이 객체는 크게 객체 헤더와 인스턴스 데이터로 나뉩니다.
- 객체 헤더: 모든 객체에 공통으로 존재하는 메타데이터 영역입니다. 주로 두 개의 '머신 워드'로 구성됩니다.
- Mark Word: 객체의 런타임 상태를 추적합니다. 해시 코드, 가비지 컬렉션(GC) 정보, 동기화 락 정보 등이 여기에 저장됩니다.
- Klass Pointer: 이 객체가 어떤 클래스의 인스턴스인지 알려주는 포인터입니다. 힙 외부에 있는 *
_klass라는 내부 구조체를 가리킵니다.
- 인스턴스 데이터: 객체에 선언된 필드의 실제 값이 저장됩니다.
2. _klass와 java.lang.Class 객체의 차이
이 두 개념은 JVM에서 클래스 정보를 다루는 방식의 핵심입니다.
_klass: JVM 내부에서 사용하는 로우레벨(Low-level) 데이터 구조체입니다. 클래스의 이름, 메서드, 필드 등 구조적 정보를 담고 있습니다._klass는 객체 인스턴스가 있는 힙 영역이 아닌 메타스페이스에 존재하며, 일반 객체와 달리 자체 헤더를 가지지 않습니다.java.lang.Class객체: 프로그래밍 관점에서 접근하는 리플렉션 객체입니다.MyClass.class처럼 우리가 코드로 다루는 대상입니다. 이 객체는 힙에 존재하며, 일반 객체처럼 헤더를 가집니다. 이 객체는_klass를 통해 클래스 정보를 가져와 개발자에게 제공합니다.
그림은 크게 왼쪽의 'Entry 객체'와 오른쪽의 'Entry 클래스'를 보여줍니다.

1. Entry 객체 (Instance)
- OOP (Entry 객체): 힙 메모리에 있는 실제 객체 인스턴스입니다.
- 객체 헤더: 이 객체의 맨 앞에는 헤더가 있습니다.
- 파란색 블록 (MarkOop): 객체의 런타임 상태(해시 코드, 락 정보 등)를 담고 있습니다.
- 회색 블록 (KlassOop): 객체의 클래스 정보를 담고 있는 KlassOop (Klass Pointer)를 가리킵니다.
- 인스턴스 데이터: xyz, 1, 3과 같은 실제 데이터가 저장되어 있습니다.
- KlassOop: 힙 외부의 특별한 영역에 있는 KlassOop 구조체를 가리키고 있습니다. 이 KlassOop 구조체에는 vtable (가상 메서드 테이블) 정보가 포함되어 있습니다.
- vtable을 통해 toString()과 같은 메서드의 실제 주소를 찾아 호출할 수 있습니다.
2. Entry 클래스 (java.lang.Class 객체)
- OOP (Entry.class): java.lang.Class 타입의 객체이며, 이 또한 힙 메모리에 존재합니다.
- 객체 헤더: 이 객체도 MarkOop와 KlassOop를 가지고 있습니다.
- KlassOop: 이 Entry.class 객체는 자기 자신의 _klass를 가리킵니다.
- 인스턴스 데이터: Entry.class 객체는 클래스 이름, 메서드 정보 등 리플렉션에 필요한 정보를 담고 있습니다. 그림에서 M0, M1...으로 표현된 메서드 목록에 대한 참조를 가지고 있습니다.
- KlassOop (Entry 클래스): 이 KlassOop 구조체 또한 vtable을 가지고 있으며, 이 테이블은 getMethod()와 같은 java.lang.Class 객체의 메서드를 가리킵니다.
3. OOP를 통한 관리
OOP는 객체와 _klass를 연결하는 핵심 고리입니다. 모든 객체 인스턴스는 헤더의 Klass Pointer를 통해 자신이 속한 클래스의 _klass를 가리켜 클래스 정보를 공유합니다. _klass에는 vtable (가상 메서드 테이블)이 포함되어 있어, 런타임에 올바른 메서드를 찾아 호출하는 데 사용됩니다. 이처럼 OOP 구조를 통해 JVM은 메모리 할당과 가비지 컬렉션을 효율적으로 관리합니다.
할당과 수명 주기
자바 가비지 컬렉션 동작을 결정하는 두 가지 주요 요인이 있습니다.
- 할당 속도 : 일정 기간 동안 새로 생성된 객체가 사용하는 메모리의 양
- 객체의 수명 주기
핫스팟의 프로덕션 가비지 컬렉션 기술
스레드-로컬 할당 버퍼 TLAB
에덴 영역 내에 할당되는 스레드별 객체 할당 공간을 의미합니다. 스레드가 각자의 영역을 갖기 때문에 동기화가 필요 없으며 덕분에 빠르게 객체 할당이 가능합니다. (가용 공간으로 포인터만 옮기면 되기 때문에 시간복잡도 O(1))
더 이상 할당한 스레드-로컬 할당 버퍼가 없을 경우, 젋은 세대 가비지 컬렉션이 발생합니다.
반구형 컬렉션
- Hemispheric은 두 개의 Survivor 영역을 사용하는 방식을 반구에 비유한 것입니다.
- Young Generation은 Eden과 두 개의 Survivor 영역(S0, S1)으로 구성됩니다.
- GC가 발생할 때마다, 살아남은 객체는 한 Survivor 영역에서 다른 Survivor 영역으로 이동합니다. 이때, 한 영역(예: S0)은 비워지고, 다른 영역(S1)에 살아남은 객체들이 쌓이게 됩니다.
- 마치 한쪽 반구(S0)에서 다른쪽 반구(S1)로 살아있는 객체들을 옮기는 것과 같아 hemispheric이라는 이름이 붙었습니다. 이는 '복사(Copying)' 가비지 컬렉션 알고리즘의 한 형태입니다.
카드 테이블(Card Table)이란?
카드 테이블은 오래된 세대(Old Generation)에서 젊은 세대(Young Generation)를 참조하는 포인터를 효율적으로 관리하기 위한 JVM의 데이터 구조입니다.
가비지 컬렉터는 젊은 세대에서 객체를 수집할 때, 루트(root) 객체로부터 도달 가능한 모든 객체를 찾아야 합니다. 이때, 루트는 스택 변수, 클래스 변수뿐만 아니라, 오래된 세대에서 젊은 세대로 향하는 참조도 포함됩니다. 만약 이 모든 참조를 매번 스캔한다면 비효율적일 것입니다.
카드 테이블은 이 문제를 해결합니다.
- 메모리 분할: 힙 메모리를 **'카드'**라는 작은 단위(예: 512바이트)로 나눕니다.
- 참조 기록: 오래된 세대 객체가 젊은 세대 객체를 참조하게 되면, 해당 오래된 세대 객체가 위치한 카드를 '더럽혀진(dirty)' 상태로 표시합니다.
- 효율적인 스캔: 마이너 GC가 발생하면, GC는 오래된 세대 전체를 스캔하지 않고 더럽혀진 상태로 표시된 카드들만 확인합니다. 이를 통해 젊은 세대로 향하는 외부 참조를 빠르게 찾아내고, 살아있는 객체를 정확하게 식별할 수 있습니다.
결론적으로, 카드 테이블은 GC의 범위를 효과적으로 좁혀 모든 객체를 순회하지 않고도 외부에서 젊은 세대를 가리키는 포인터를 효율적으로 추적하는 핵심 기술입니다.
병렬 컬렉터
젊은 병렬 컬렉션 (Parallel Young Collector)
젊은 병렬 컬렉터는 **Young Generation(Eden, Survivor 영역)**에서 객체를 회수할 때 여러 스레드를 사용합니다. 이 컬렉터는 'Parallel Scavenge Collector'라고도 불립니다.
- 동작 방식: 마이너 GC(Minor GC)가 발생하면, 애플리케이션 스레드는 모두 멈추고(Stop-The-World), GC 스레드들이 살아있는 객체를 동시에 찾아내어 다른 영역으로 복사하는 작업을 병렬로 수행합니다.
- 목표: GC의 처리량을 최대화하는 데 중점을 둡니다. 즉, GC가 전체 CPU 시간에서 차지하는 비중을 줄이는 것을 목표로 합니다.
- 주요 특징: TLAB(Thread-Local Allocation Buffer)와 반구형 대피(Hemispheric Evacuating) 방식을 사용하여 객체 할당 및 회수를 최적화합니다.
오래된 병렬 컬렉션 (Parallel Old Collector)
오래된 병렬 컬렉터는 Old Generation에서 객체를 회수할 때 여러 GC 스레드를 사용합니다. 이 컬렉터는 'Parallel Scavenge Collector'와 함께 사용되어 전체 GC 처리량을 높이는 데 기여합니다.
- 동작 방식: 메이저 GC(Major GC)가 발생하면, 여러 GC 스레드가 동시에 힙 전체를 스캔하고 살아남은 객체를 압축(Compacting)하여 메모리 단편화를 제거합니다.
- Young Collector는 반구형이지만, Old Collector는 단일 연속 메모리 공간을 사용하는 압축형 컬렉터
- 목표: Young GC와 마찬가지로 GC의 처리량을 높여 애플리케이션의 처리 속도를 높이는 데 중점을 둡니다.
- 한계: Young GC 영역 보다 훨씬 많은 메모리를 차지함 → GC가 발생할 경우 메모리 크기와 비례하여 STW 시간 증가
직렬 컬렉터 (Serial Collector)
직렬 컬렉터는 GC 작업을 단일 스레드로 처리하는 가장 단순한 형태의 컬렉터입니다.
- 동작 방식: Young Generation(Minor GC)이든 Old Generation(Major GC)이든 모든 GC 작업은 하나의 스레드만 담당합니다. GC가 실행되는 동안 애플리케이션은 완전히 중단됩니다.
- 적합 환경: 단일 코어 환경이나 메모리가 매우 작아 GC 일시정지 시간이 큰 문제가 되지 않는 소규모 애플리케이션에 적합합니다.
- 특징: 단순한 구조 덕분에 GC 오버헤드가 적고 구현이 간단하지만, 멀티코어 환경에서는 병렬 컬렉터보다 성능이 떨어집니다.
'📗Java' 카테고리의 다른 글
| [자바최적화] GC 알고리즘 (0) | 2025.11.17 |
|---|---|
| [자바최적화] JVM 개요 (2) | 2025.08.16 |
| [자바 최적화] 최적화와 성능 정의 (1) | 2025.08.10 |
| [Java] static class 와 static method 이해하기 (3) | 2024.09.21 |
| [Java] Virtual Thread 알아보기 (0) | 2024.05.23 |