
이전 [Inside Java] 시리즈에서는 class로 만든 객체와 메서드 호출이 JVM 안에서 어떻게 연결되는지 살펴봤습니다.
그런데 Java에는 class만 있는 것이 아니죠.....
interface Animal {
void speak();
}
enum Direction {
LEFT,
RIGHT
}
@interface Marker {
}
셋은 문법적으로 서로 완전히 달라 보입니다. 그런데 컴파일하고 나면 모두 .class 파일이 만들어집니다.
Animal.java → Animal.class
Direction.java → Direction.class
Marker.java → Marker.class
여기서 한 가지 궁금한 점이 생겼습니다.
이름은 모두
.class인데,
그렇다면 전부 일반 클래스처럼 Heap에 객체가 만들어지는 걸까요?
이번 글은 잠시 쉬어가는 느낌으로, interface, enum, class file 안에서는 어떻게 표현되고 실제 런타임에서는 무엇이 객체로 생성되는지 정리해보려고 합니다.
.class는 class 키워드만을 위한 파일이 아니다
.class라는 확장자 때문에 Java의 class 선언만 저장하는 파일처럼 느껴질 수 있습니다. (제가 그랬었습니닷,,)
하지만 JVM이 읽는 것은 Java 문법 그 자체가 아니라 Class File Format입니다.
Class File Format 안에는 이 타입이 어떤 종류인지 구분하는 접근 플래그와 메타데이터가 들어갑니다.
chapter 04. The ClassFile Format(링크. 클릭하면 바로 이동되요!) 사이트에 보면은 ClassFile structure를 알 수 있습니다.
/// 대략적으로,
Class File
├── Magic Number
├── Version
├── Constant Pool
├── Access Flags
├── 현재 타입 정보
├── 부모 타입과 인터페이스 정보
├── Field 정보
├── Method 정보
└── Attribute 정보
그 중, access_flags를 통해 이 타입이 일반 클래스인지, 인터페이스인지, enum인지, annotation 타입인지 구분할 수 있습니다.
일반 class → 일반 클래스 형태
interface → ACC_INTERFACE
annotation → ACC_ANNOTATION + ACC_INTERFACE
enum → ACC_ENUM
정확한 플래그 조합과 세부 규칙은 Class File Format에 정의되어 있지만, 여기서 중요한 점은 다음과 같습니다.
JVM이 실행하는 단위는 Java 소스의
class키워드가 아니라,
JVM이 이해할 수 있는 class file 형식( .class 파일 )입니다.
그래서 interface, enum, annotation도 모두 .class가 됩니다.
사이트 안에 있는 4.1 The ClassFile Structure 이 부분은 건너 뛰어도 되지만 궁금한 분들을 위해서 정리했습니다.

좀 더 자세히 보자면은,, (여기서 u4는 4바이트, u2는 2바이트 크기의 데이터를 의미!)
magic- 이 파일이 올바른 자바
.class파일인지 확인하기 위한 매직 넘버(Magic Number) 담김.
항상 고정된 값인0xCAFEBABE있음 - → 이거는 ClassLoader가 막 항상 확인했었던게 .class이게 이상한걸수도있으니까 고걸 위한 필드죠!
- 이 파일이 올바른 자바
minor_version,major_version- 이 클래스 파일을 컴파일한 자바 컴파일러의 버전 정보가 담깁니다. JVM은 이 버전을 보고 현재 실행 환경과 호환되는지 체크
constant_pool_count&constant_pool- 클래스 내에서 사용하는 모든 상수들의 모음.
문자열 리터럴, 클래스/인터페이스 이름, 메서드 및 필드 이름/타입 등이 번호(인덱스) 형태로 저장.
파일 안에서 공용으로 참조됨.
- 클래스 내에서 사용하는 모든 상수들의 모음.
access_flags- 이 클래스/인터페이스의 접근 제어자, 속성 flag담김.
e.g.public,final,abstract,interface인지 여부 등
- 이 클래스/인터페이스의 접근 제어자, 속성 flag담김.
this_class- 현재 클래스의 이름을 나타내는 상수 풀(Constant Pool)의 인덱스 번호 담김.
super_class- 부모 클래스(Superclass) 이름을 나타내는 상수 풀의 인덱스 번호 담김. (자바의 모든 클래스는 기본적으로
Object를 상속받음)
- 부모 클래스(Superclass) 이름을 나타내는 상수 풀의 인덱스 번호 담김. (자바의 모든 클래스는 기본적으로
interfaces_count&interfaces- 이 클래스가 구현(implements)한 인터페이스들의 개수와, 각 인터페이스를 가리키는 상수 풀 인덱스 목록이 담김 : )
fields_count&fields- 이 클래스에 선언된 멤버 변수(필드)들의 개수와 상세 정보(
field_info)들이 담김.
변수의 이름, 타입, 접근 제어자(private,static등)가 포함됨
- 이 클래스에 선언된 멤버 변수(필드)들의 개수와 상세 정보(
methods_count&methods- 이 클래스에 선언된 모든 메서드(함수)들의 개수와 상세 정보(
method_info)들이 담김.
함수의 경우 로직이 있으니까.. 그 구현부 → 실제 함수 로직(바이트코드)도 이 안의Code속성에 포함되어 있음!
- 이 클래스에 선언된 모든 메서드(함수)들의 개수와 상세 정보(
attributes_count&attributes- 클래스 자체에 딸려 있는 부가적인 정보(
attribute_info)들의 개수와 목록이 담김.
대표적으로 소스 파일 이름(SourceFile)이나 어노테이션 정보 etc…
- 클래스 자체에 딸려 있는 부가적인 정보(
Q. interface는 Heap에 객체가 만들어질까?
먼저 일반 추상 메서드와 default 메서드를 함께 가진 인터페이스를 살펴보겠습니다.
interface Animal {
// 구현 클래스가 반드시 구현해야 하는 추상 메서드
void speak();
// 인터페이스가 직접 구현을 제공하는 default 메서드
default void sleep() {
System.out.println("잠을 잔다");
}
}
그리고 Animal을 구현하는 Dog 클래스를 만듭니다.
class Dog implements Animal {
private String name;
Dog(String name) { this.name = name; }
@Override
public void speak() {
System.out.println(name + "이 멍멍 짖는다");
}
}
Dog는 speak()만 직접 구현했습니다.
반면 sleep()은 오버라이드하지 않았으므로, Animal 인터페이스가 제공하는 default sleep() 구현을 그대로 사용합니다.
public class Main {
public static void main(String[] args) {
Animal animal = new Dog("Mong");
animal.speak();
animal.sleep();
}
}
// 실행 결과
Mong이 멍멍 짖는다
잠을 잔다.
여기서 궁금한 점이 생깁니다.
Dog.speak()의 바이트코드는 어디에 저장될까?Animal.sleep()의 바이트코드는Dog객체 안으로 복사될까?- Heap의
Dog객체는Animal인터페이스를 어떻게 찾아갈까? animal.sleep()을 호출하면 JVM은 어떤 경로로 실행 코드를 찾을까?
이 구조를 JVM의 Stack, Heap, Method Area 관점에서 살펴보겠습니다.
interface 자체의 인스턴스는 만들 수 없다!
인터페이스도 컴파일하면 .class 파일이 만들어집니다.
Animal.java → Animal.class
Dog.java → Dog.class
Main.java → Main.class
하지만 다음 코드는 컴파일되지 않습니다.
Animal animal = new Animal(); // 컴파일 오류
Animal.class가 존재한다고 해서 Heap에 Animal Object를 직접 만들 수 있는 것은 아닙니다.
실제로 Heap에 생성되는 것은 구현 클래스의 인스턴스입니다.
Animal animal = new Dog("Mong");
이 코드에서는 Dog 객체 하나가 Heap에 만들어지고, 지역 변수 animal이 그 객체를 참조합니다.
animal의 선언 타입 → Animal
실제 생성된 객체 → Dog
따라서 Animal은 참조 변수의 타입이자 메서드 계약으로 사용되고, 실제 인스턴스는 Dog입니다.
Animal.class와 Dog.class에는 무엇이 들어갈까?
컴파일 직후의 .class 파일을 개념적으로 단순화하면 다음과 같습니다.
Animal.class
├── ACC_INTERFACE, ACC_ABSTRACT
├── interface 이름: Animal
├── speak() 선언 정보
│ └── 구현 바이트코드 없음 (메서드 구현부가 없으니 당연!)
├── sleep() 메서드 정보
│ └── Code Attribute
│ └── default 메서드 바이트코드 (sleep() 내부 로직이 바이트코드로)
└── Constant Pool
일반 추상 메서드인 speak()에는 메서드 이름과 매개변수, 반환 타입 등의 선언 정보가 있지만 실행할 구현 바이트코드는 없습니다.
반면 default sleep()에는 실제 구현이 있으므로 Animal.class의 method_info 안에 Code 속성과 바이트코드가 들어갑니다.
Dog.class
├── 클래스 이름: Dog
├── 부모 클래스: Object
├── 구현 인터페이스: Animal
├── name 필드 정보
├── Dog(String) 생성자 바이트코드
├── speak() 메서드 바이트코드
└── Constant Pool
Dog.class에는 Dog가 직접 선언한 speak()의 바이트코드가 들어갑니다.
하지만 Dog가 오버라이드하지 않은 Animal.sleep()의 바이트코드가 Dog.class 안으로 복사되는 것은 아닙니다.
Dog.speak() 구현
→ Dog.class에 존재
Animal.sleep() default 구현
→ Animal.class에 존재
각 class file은 하나의 클래스 또는 인터페이스 정의를 표현하며, 클래스와 인터페이스가 로딩되면 JVM은 이를 실행 중 사용할 내부 표현으로 구성합니다.
로딩된 뒤에는 Method Area에 타입별 메타데이터가 만들어짐!!
JVM이 Animal.class와 Dog.class를 로딩하면, 각 타입에 대응하는 런타임 메타데이터가 구성됩니다.
JVM 명세 관점에서는 이러한 타입 정보와 메서드 정보가 Method Area에 존재합니다.
Method Area
Animal Interface Metadata
├── 타입 이름: Animal
├── 접근 플래그: interface, abstract
├── 상위 인터페이스 정보
├── Runtime Constant Pool
└── Method Metadata
├── speak()V
│ └── 추상 메서드 선언
└── sleep()V
└── 실행 가능한 바이트코드
Dog Class Metadata
├── 타입 이름: Dog
├── 부모 타입: Object
├── 구현 인터페이스: Animal
├── Field Metadata
│ └── name : String
├── Runtime Constant Pool
└── Method Metadata
├── <init>(String)V
└── speak()V
└── 실행 가능한 바이트코드
여기서 중요한 점은 Animal Interface Metadata와 Dog Class Metadata가 서로 완전히 독립된 정보 덩어리로 끝나는 것이 아니라는 점입니다.
Dog Class Metadata에는 Dog가 Animal을 구현한다는 타입 관계가 기록됩니다.
Dog Class Metadata
└── implements information
└── Animal Interface Metadata
따라서 JVM은 Dog 객체의 실제 클래스 정보를 확인한 뒤, Dog가 어떤 인터페이스를 구현하는지와 해당 인터페이스 메서드의 실제 구현이 무엇인지 탐색할 수 있습니다.
HotSpot JVM에서는 Method Area가 하나의 Java 객체 형태로 구현되는 것은 아닙니다. 클래스 메타데이터는 주로 Heap 바깥의 네이티브 메모리인 Metaspace에서 관리되며, 내부적으로 InstanceKlass, 메서드 메타데이터, 인터페이스 호출 정보 등의 여러 구조가 연결되어 동작합니다. 이런 관계를 살펴보고 싶으면 이전에 정리한 Inside Java 3편 포스트 [링크] 한번 보시는 것도 좋습니다!
따라서 글에서는 다음처럼 구분해서 이해하면 좋습니다.
JVM 명세의 논리적 표현
→ Method Area
HotSpot의 실제 구현 관점
→ Metaspace와 여러 JVM 내부 구조
Heap의 Dog 객체에는 무엇이 저장될까?
다음 코드가 실행되면 Heap에는 Dog 객체가 만들어집니다.
Animal animal = new Dog("Mong");
HotSpot JVM 관점에서 단순화한 Dog 객체의 구조는 다음과 같습니다.
Dog Object
├── Object Header
│ ├── Mark Word
│ └── Klass Pointer
├── Parent declared fields
│ └── 없음
└── Dog declared fields
└── name → "Mong"
여기서 name은 Java 코드에서 선언한 실제 인스턴스 필드입니다.
private String name;
반면 Klass Pointer는 개발자가 Java 코드로 선언한 필드가 아닙니다.
class Dog {
// Klass Pointer라는 Java 필드가 선언되어 있는 것이 아님
}
Klass Pointer는 JVM이 객체의 실제 타입을 식별하기 위해 Object Header에 관리하는 내부 연결 정보입니다.
개념적으로는 다음 관계를 만듭니다.
Heap의 Dog Object
│
└── Object Header의 Klass Pointer
│
▼
Dog Class Metadata
정리를 한번 더 다시 해보면, Dog 객체 안의 name 필드가 Dog Class Metadata나 Animal Interface Metadata를 가리키는 것이 아닙니다.
메타데이터로 이어지는 출발점은 인스턴스 필드가 아니라 Object Header의 Klass Pointer입니다.
HotSpot의 전통적인 객체 헤더 구조에서는 객체 헤더에 Mark Word와 Klass Pointer!가 있고, 이 Klass 정보가 객체의 클래스 메타데이터와 연결됨. 다만 객체 헤더의 구체적인 배치는 JVM 옵션과 HotSpot 버전에 따라 죠금씩 달라짐
Stack, Heap, Method Area의 전체 관계
Animal animal = new Dog("Mong");이 실행된 상태를 단순화하면 다음과 같습니다.

이 그림에서 핵심 연결은 다음 세 가지입니다.
main Stack Frame의 animal
→ Heap의 Dog Object 참조
Dog Object의 Klass Pointer
→ Dog Class Metadata 연결
Dog Class Metadata의 implements / dispatch 정보
→ Animal Interface Metadata 및 실제 구현 메서드 연결
Animal.sleep()의 바이트코드가 Dog Object 안에 들어가는 것도 아니고, Dog Object가 Animal Interface Metadata를 직접 가리키는 전용 Java 필드를 가지는 것도 아닙니다.
먼저 객체의 Klass Pointer를 통해 Dog Class Metadata에 도달하고, 그 클래스의 타입 관계와 인터페이스 디스패치 정보를 바탕으로 호출할 메서드를 찾는 구조로 이해할 수 있습니다.
일반 인터페이스 메서드는 어디서 실행될까?
Animal.speak()는 인터페이스에 선언만 있고 구현은 없습니다.
따라서 호출 시 JVM은 실제 객체인 Dog의 클래스 메타데이터에서 Dog.speak() 구현을 찾아 실행합니다.
즉 인터페이스는 호출 가능한 메서드의 규칙을 제공하고, 실제 구현 코드는 구현 클래스에 존재합니다.
그럼 default 메서드는 어디서 실행될까?
default sleep()의 구현 바이트코드는 Dog가 아니라 Animal 인터페이스의 메타데이터에 존재합니다.
Dog가 sleep()을 오버라이드하지 않았다면 JVM은 Animal의 default 구현을 선택합니다.
따라서 default 메서드가 Dog 객체나 Dog.class 안으로 복사되는 것은 아닙니다.
여러 클래스가 같은 인터페이스를 구현하면?
class Dog implements Animal { ... }
class Cat implements Animal { ... }
Dog.speak()와 Cat.speak()는 각각 자신의 클래스 메타데이터에 저장됩니다.
반면 두 클래스가 sleep()을 오버라이드하지 않았다면, 모두 동일한 Animal.sleep() default 구현을 사용합니다. 각 객체는 자신의 실제 클래스에 따라 일반 메서드를 선택하고, 필요하면 인터페이스의 default 구현을 공유합니다.
메모리 관계 정리
Dog Object
→ Dog Class Metadata
→ Dog.speak() 구현
Dog Object
→ Dog Class Metadata
→ Animal Interface Metadata
→ default sleep() 구현
Heap의 객체는 메서드 코드를 직접 가지고 있지 않습니다.
객체의 클래스 정보를 기준으로 구현 클래스를 확인하고, default 메서드가 필요하면 인터페이스 메타데이터의 구현을 찾아 실행합니다.
요약 하자면,
여러 클래스가 같은 인터페이스의 default 메서드를 오버라이드하지 않으면, 각 클래스의 메타데이터는 해당 인터페이스 구현 관계를 가지고 있고, 호출 시 공통으로 인터페이스 메타데이터에 있는 하나의 default 메서드 구현을 사용합니다.
// method area
Dog Object → Dog Metadata ─┐
Cat Object → Cat Metadata ─┼→ Animal Interface Metadata
Bird Object → Bird Metadata┘ └→ default sleep() Bytecode
즉 여러 클래스가 하나의 구현을 공유하므로 코드 재사용의 성격이 있습니다. 다만 일반적인 다형성과 함께 동작하지만, 핵심 목적은 인터페이스에 기본 구현을 제공하는 것입니다.
특정 클래스가 default 메서드를 오버라이드하면 그 클래스의 .class와 클래스 메타데이터에 새로운 구현 바이트코드가 생깁니다.
Dog Metadata → Dog.sleep() Bytecode
Cat Metadata → Animal.sleep() default Bytecode
따라서 Dog 객체에서는 Dog.sleep()이 선택되고, 오버라이드하지 않은 Cat 객체에서는 여전히 Animal.sleep()이 선택됩니다. 인터페이스와의 타입 관계 자체가 사라지는 것은 아니고, 해당 메서드 호출 대상만 클래스의 오버라이드 구현으로 바뀌는 것입니다.
메모리를 정말 잘 활용하는 구조인 것 같습니다.
interface의 static 메서드는 호출 구조가 다르다!!
인터페이스의 static 메서드 구현 바이트코드는 해당 인터페이스의 .class와 런타임 때 메타데이터에 존재합니다.
호출할 때는 Animal.printType()처럼 인터페이스 이름으로 직접 호출하며, 실제 객체나 Klass Pointer를 확인하지 않습니다. 호출 대상이 이미 Animal.printType()으로 확정되어 있으므로 JVM은 invokestatic으로 해당 메서드 구현을 바로 실행합니다.
따라서 default 메서드처럼 실제 객체 타입을 기준으로 동적으로 구현을 찾는 과정이 필요하지 않습니다.
여기서 퀴즈!
interface에서 default의 경우 어떤가요? 이를 구현하는 구현체에서 오버라이딩 할수도 있고, 안할수도 있죠?
그럼 이 경우는 static disaptch일까요? dynamic dispatch인가요? 그렇다면 그냥 interface안에 추상 메서드로 선언만 된 함수는 static disaptch일까요? dynamic dispatch 일까요?
인터페이스에서 static을 구현하면은 이는 static disaptch인가요? dyanmic dispatch인가요?
결과적으로 interface에 선언만 된 추상 메서드, default 메서드는 이 인터페이스를 구현한 클래스나 상속받은 하위 클래스어디에서 호출될지는 런타임 때 힙 영역에 new로 할당된 인스턴스 객체 타입을 기준으로 구현 메서드를 선택해서 dynamic dispatch가 정확해 보입니닷! 반면 interface의 static 함수는은 컴파일 단에서 추론 가능한 static disaptch입니다.
interface 메서드의 저장 위치를 정리하면
지금까지의 내용을 메서드 종류별로 정리하면 다음과 같습니다.
| 인터페이스 메서드 | 구현 바이트코드 | 실제 실행 대상 |
|---|---|---|
void speak(); |
인터페이스에 없음 | 구현 클래스의 speak() |
default void sleep() |
인터페이스에 있음 | 오버라이드가 없으면 인터페이스의 sleep() |
static void printType() |
인터페이스에 있음 | Animal.printType()으로 직접 호출 |
메모리 관계도 함께 정리하면,
- Heap의 객체는 인스턴스 상태를 가진다!
- Method Area의 클래스·인터페이스 메타데이터는 타입 관계와 메서드 구현 정보를 가진다!
- JVM은 실제 객체의 클래스 정보를 기준으로 최종 실행할 메서드를 선택!
enum은 Heap에 객체가 만들어질까?
enum Direction {
LEFT,
RIGHT
}
enum은 단순한 정수 상수 묶음이 아닙니다. 각 enum 상수는 실제 객체(Object)입니다. 컴파일러가 만드는 정확한 코드는 구현 세부사항이지만, 개념적으로는 다음과 비슷하게 이해할 수 있습니다.
final class Direction extends Enum<Direction> {
public static final Direction LEFT
= new Direction("LEFT", 0);
public static final Direction RIGHT
= new Direction("RIGHT", 1);
}
즉 LEFT, RIGHT는 Direction 타입의 객체를 가리키는 static final 참조라고 볼 수 있습니다.
Direction Class
├── static final LEFT ─────┐
└── static final RIGHT ───┐ │
│ │
Heap │ │
├── Direction LEFT Object ◀─┘
└── Direction RIGHT Object ◀
enum 상수 객체는 일반적으로 해당 enum 타입의 클래스 초기화 과정에서 만들어집니다.
Direction 클래스 초기화
↓
LEFT 객체 생성
↓
RIGHT 객체 생성
↓
static final 필드에 참조 저장
따라서 다음 코드는 매번 새 객체를 생성하는 것이 아닙니다.
Direction first = Direction.LEFT;
Direction second = Direction.LEFT;
System.out.println(first == second); // true
first와 second는 이미 만들어진 동일한 LEFT 객체를 가리킵니다.
이 때문에 enum은 == 비교를 사용할 수 있고, 정해진 인스턴스만 존재하는 타입으로 활용됩니다.
정리하면,
Direction이라는 하나의 큰 Heap 객체 안에 LEFT, RIGHT가 필드로 들어가는 구조가 아닙니다. 그대신 Direction 클래스의 static final 필드가 Heap에 생성된 각 enum 객체를 참조합니다.,
Direction.LEFT를 여러 번 사용해도 매번 새 객체가 생기는 것은 아니고, 이미 만들어진 동일 객체를 참조합니다.
그리고 enum 상수마다 다른 동작도 가질 수 있습니다.
enum Operation {
PLUS {
@Override
int apply(int a, int b) {
return a + b;
}
},
MINUS {
@Override
int apply(int a, int b) {
return a - b;
}
};
abstract int apply(int a, int b);
}
이 경우 enum 상수마다 다른 메서드 구현을 가질 수 있습니다.
이때 컴파일 결과에서는 enum 본체 외에 상수별 동작을 표현하기 위한 추가 class file이 생성될 수도 있습니다.
Operation.class
Operation$1.class
Operation$2.class
enum 역시 JVM 관점에서는 클래스 메타데이터와 실제 Heap 객체를 사용하는 타입입니다.
문법은 특별하지만, 런타임에서는 객체와 메서드 호출이라는 기존 JVM 구조 위에서 동작한다고 볼 수 있습니다.
한 번에 정리하기
| 종류 | .class 생성 |
일반적인 Heap 객체 | 핵심 특징 |
|---|---|---|---|
class |
생성됨 | new로 인스턴스 생성 |
필드와 메서드를 가진 일반 객체 |
interface |
생성됨 | 인터페이스 자체 인스턴스는 생성 불가 | Heap에는 구현 클래스 객체가 생성됨 |
enum |
생성됨 | enum 상수가 실제 객체로 생성됨 | 미리 정해진 인스턴스를 공유 |
흐름을 간단히 모으면 다음과 같습니다.
Java Source
├── class
├── interface
├── enum
└── annotation
↓ javac
JVM Class File Format
↓ ClassLoader
클래스 메타데이터 구성
↓ 필요에 따라
Heap 객체 생성
모두 .class가 되지만, Heap에 객체가 만들어지는 방식은 서로 다릅니다.
- 인터페이스는 구현 클래스의 객체가 생성됩니다.
- enum은 선언된 상수들이 실제 객체로 생성됩니다.
- 애노테이션은 먼저 메타데이터로 저장되고, 런타임 조회 시 객체처럼 사용할 수 있는 표현이 제공될 수 있습니다.
핵심 정리
.class는 class 키워드로 선언한 타입만을 위한 파일이 아니라, JVM이 클래스·인터페이스·enum을 표현하는 공통 실행 형식입니다.
interface도.class로 컴파일되지만 인터페이스 자체를new로 생성할 수는 없습니다. Heap에는 이를 구현한 클래스의 인스턴스가 생성됩니다.- 인터페이스의 추상 메서드 구현은 구현 클래스의 메타데이터에 존재하고,
default메서드의 구현은 인터페이스 메타데이터에 존재합니다. - 추상 메서드와
default메서드는 실제 객체 타입에 따라 구현이 선택되므로 Dynamic Dispatch로 호출됩니다. 반면static메서드는 호출 대상이 정해져 있어 Static Dispatch로 호출됩니다. enum의 각 상수는 단순한 정수가 아니라 Heap에 생성되는 서로 다른 enum 객체입니다. 같은 상수를 여러 번 사용해도 이미 생성된 동일 객체를 공유합니다.
결국 모두 .class로 컴파일되지만, Heap에 객체가 생성되는 방식과 메서드 구현을 선택하는 방식은 타입마다 다릅니다.
[Inside Java] 몰아보기
[Inside Java] 01. Java는 왜 바로 기계어로 컴파일하지 않을까? — 바이트코드부터 JVM 메모리까지...
Swift로 iOS 앱을 만들 때는 보통 컴파일 → 빌드 → 실행 파일 생성 → CPU 실행이라는 흐름이 비교적 자연스럽게 느껴졌습니다.C++도 비슷합니다. 소스 코드를 컴파일하면 특정 운영체제와 CPU가 이
dev-with-precious-dreams.tistory.com
[Inside Java] 02. 업캐스팅?! Java 컴파일러는 무엇을 알고, JVM은 무엇을 결정할까? — Dynamic Dispatch로
Parent p = new Child(); Java를 공부하다 보면 이 한 줄을 아주 자주 만나게 됩니다.보통은 이렇게 설명합니다.부모 타입 변수에 자식 객체를 담는 업캐스팅이다. 문법적으로는 맞는 설명입니다.그런데
dev-with-precious-dreams.tistory.com
[Inside Java] 03. Heap 객체에서 method Area까지! - Klass Pointer와 Dynamic vs Static Dispatch 살펴보기
이전 포스트(누르면 바로가져요) 에서는 다음 코드가 왜 문법적으로 가능한지 살펴봤습니다.Parent p = new Child(); 컴파일러는 표현식 p의 정적 타입을 Parent로 보고, 실제 생성되는 객체는 Child입니
dev-with-precious-dreams.tistory.com
[Inside Java] 04. JVM은 main()을 바로 실행하지 않는다?! #1 -ClassLoader, 클래스 생명주기 #Loading #Linking #In
1편에서는 Java 코드가 바이트코드로 변환되고 JVM 위에서 실행되는 전체 흐름을 살펴봤습니다. 2편에서는 컴파일러가 정적 타입을 기준으로 무엇을 검사하고, 런타임의 JVM이 실제 객체를 기준으
dev-with-precious-dreams.tistory.com
[Inside Java] 05. JVM은 main()을 바로 실행하지 않는다 #2 — class metadata는 언제 초기화 될까? #Active Use,
앞선 4장에서는 .class 파일이 JVM 내부에서 Loading, Linking, Initialization을 거쳐 실행 가능한 타입이 되는 과정을 살펴봤습니다.하지만 여기서 한 가지 궁금증이 생깁니다. “클래스가 메모리에 로드
dev-with-precious-dreams.tistory.com
[Inside Java] 06. 같은 Java 코드가 실행될수록 빨라지는 이유 — JIT 컴파일과 HotSpot JVM 최적화 #Inline #
[Inside Java] 1~5편 까지는 JVM이 메모리를 어떻게 관리하는지를 중심으로 살펴보았습니다. 그런데 지금까지 살펴본 것처럼 JVM이 런타임마다 바이트코드를 하나씩 해석하고, Operand Stack을 이용해 연
dev-with-precious-dreams.tistory.com
[Inside Java] 08. 마무리.. Java 실행 구조 핵심 위키!
앞선 1장부터 7장까지 다룬 내용을 다시 찾기 쉽도록 핵심 개념만 두줄로 압축했습니다.자세한 동작 원리와 예시는 각 챕터에서 확인하고, 이 글에서는 개념의 연결 관계만 빠르게 정리합니다.
dev-with-precious-dreams.tistory.com
References
- JVM Specification — The Class File Format
https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html`.class`
구조와ACC_INTERFACE,ACC_ENUM,ACC_ANNOTATION등의 플래그 설명 - JVM Specification — Run-Time Data Areas
https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html
Heap, Method Area, Runtime Constant Pool 등 JVM 런타임 영역 설명 - Java Language Specification — Interfaces / Classes
https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html
인터페이스의 추상,default,static메서드 규칙을 설명.default메서드는 클래스가 오버라이드하지 않으면 상속될 수 있음