Primitive Type vs Reference Type
타입의 정의와 분류
언어 차원
- Primitive Type : byte, short, int, long, float, double, char, boolean
- Reference Type : 모든 클래스, 인터페이스, 배열, enum ...
JVM 차원
JVM은 Primitive Type을 명확하게 별도의 명령어 세트로 분리함
int의 경우 iadd , istore, iload 등으로 처리되며, 객체 참조는 aload, putfield, invokevirtual 등으로 처리됨
Reference Type은 Heap영역의 객체 주소를 조작하는 방식이며, primitive type은 명령어 수준에서 값을 다룸
메모리 모델과 저장 위치
항목 | Primitive Type | Reference Type |
변수 저장 위치 (지역 변수) | Stack | Stack (참조값) + Heap (객체 본체) |
필드 저장 위치 (클래스 / 인스턴스) | Heap | Heap |
저장 내용 | 값 자체 | 객체 주소 |
JVM 스택 프레임 내부에는 LocalVariableTable이 존재하며, Primitive Type은 그 안에 직접 저장됨.
그에 반해 Reference Type은 Heap에 존재하는 객체를 가리키는 참조값만 저장됨
바이트코드 및 명령어 수준의 차이
Primitive Type은 JVM 명령어 자체에 최적화 되어있음
이에 반해 Reference Type은 객체 생성, 메서드 호출이 반복되어 런타임 비용이 증가
(Boxing / UnBoxing)의 경우 JIT 최적화 대상이 아니면 병목이 될 수 있음
int x = 3, y = 5;
int sum = x + y;
위 코드는 다음과 같은 바이트 코드로 컴파일 됨
iload_1 // x
iload_2 // y
iadd // x + y
istore_3 // sum
Integer x = 3, y = 5;
Integer sum = x + y;
Reference Type은 다음과 같은 추가적인 연산이 포함됨
invokestatic Integer.valueOf(int) -> boxing
invokevirtual Integer.intValue() -> unboxing
invokestatic Integer.valueOf(int) -> re-boxing
GC 관점의 근본적인 차이
Primitive Type의 경우 Stack 혹은 Heap의 객체 내에서 값 자체로 존재함.
이에 따라 Primitive Type은 GC 대상이 아니다.
Reference Type의 경우 다음 조건 하에서 GC 대상이 됨
- 더 이상 GC Root에 의해 도달 불가능 할 때
- 참조 그래프 상에서 연결이 단절 될 때
- Soft/Weak/Phantom Reference에 따라 처리 우선순위가 조정됨
타입 시스템의 동등성과 동일성
Primitive Type
== 는 값 비교. 불변하며 의미가 명확
Reference Type
- == : 주소 값을 비교
- equals : 객체의 상태를 비교
Java의 Object 클래스는 equals()와 hashCode()를 모두 정의하고 있으며, Collection API에서 key 동작에 핵심적
따라서 Reference Type 사용 시 equals/hashCode는 항상 함께 재정의 해야 함
Type Erasure와 Generic의 제약
Java의 Generic은 타입 소거 기반이기 때문에 컴파일 이후에는 타입 정보가 유지되지 않음
이로 인해 다음과 같은 제약이 존재함
List<int> list = new ArrayList<>(); // compile error
List<Integer> list = new ArrayList<>(); // ok
JIT, Escape Analysis, Stack Allocation
HotSpot JVM은 Escape Analysis를 기반으로 객체가 메서드 밖으로 나가지 않는다고 판단되면 Stack Allocation을 시도함.
이는 Reference Type을 Stack에 할당하여 GC 부하를 줄이는 기술.
반면 Primitive Type은 애초에 Stack에 값 자체로 저장되므로 이러한 최적화와 무관
그러나 다음 조건에서는 Reference Type이라도 Stack에 할당이 가능함
- 객체가 메서드 내에서만 사용됨
- 멀티스레드 간 공유되지 않음
- JIT 컴파일러가 해당 최적화를 사용