자바 언어·플랫폼 용어 사전
언어 기초primitive · reference · 스택과 힙

원시 타입·참조 타입

값을 직접 담느냐 주소를 담느냐의 차이. 대입·비교·null 가능 여부가 전부 여기서 갈린다.

자바 변수는 두 종류뿐이다 — 값을 직접 담는 것객체의 주소를 담는 것.

메모리에서 무엇이 다른가

int a = 10;              // 스택의 변수 칸에 10 이 직접 들어간다
String s = "hi";         // 스택의 변수 칸에는 '주소' 가 들어가고 실체는 힙에 있다
[스택]                    [힙]
 a │  10   │              ┌──────────┐
 s │ 0x1A4 │ ───────────→ │ "hi"     │
                          └──────────┘

대입할 때 무엇이 복사되나

int a = 10, b = a;       // 값 10 이 복사된다. 이후 서로 무관
b = 20;                  // a 는 여전히 10

int[] x = {1,2,3}, y = x;  // '주소' 가 복사된다. 같은 배열을 가리킨다
y[0] = 99;                 // x[0] 도 99 가 된다

자바는 항상 값 전달(pass by value) 이다. 다만 참조 타입에서는 그 값이 주소라서 호출된 메서드가 같은 객체를 건드릴 수 있다.

void f(int[] arr) { arr[0] = 99; }      // 호출자에게 보인다 (같은 객체)
void g(int[] arr) { arr = new int[]{7}; }  // 안 보인다 (지역 변수만 다른 곳을 가리킴)

비교가 갈린다

int a = 128, b = 128;
a == b;                  // true — 값을 비교한다

Integer x = 128, y = 128;
x == y;                  // false! — 주소를 비교한다
x.equals(y);             // true

여덟 개의 원시 타입

  • boolean — (크기 미정의)

  • byte — 1B short 2B int 4B long 8B

  • char — 2B (UTF-16 코드 유닛)

  • float 4B — double 8B

  • 나머지는 전부 참조 타입 — 클래스 · 인터페이스 · 배열 · enum

char가 2바이트라는 점이 함정이다. 이모지 같은 보조 문자는 2 코드 유닛을 차지해 String.length()가 사람이 세는 글자 수와 달라진다.

"👍".length();                    // 2
"👍".codePointCount(0, 2);        // 1

null은 참조 타입만 가진다

int i = null;            // 컴파일 에러
Integer i = null;        // 가능 → 그래서 언박싱에서 NPE 가 난다

면접 함정

  • "자바는 참조 전달을 지원한다" → 항상 값 전달이다. 참조 을 전달할 뿐이다.
  • "원시 타입은 힙에 없다" → 객체의 필드로 있으면 그 객체와 함께 힙에 있다. 지역 변수일 때만 스택이다.

객체 하나가 실제로 몇 바이트인가

  • 64비트 JVM · 압축 OOP 기준

  • 객체 헤더 — 12바이트 (mark word 8 + klass pointer 4)

  • 필드 — int 4 · long 8 · 참조 4 (압축 시)

  • 정렬 패딩 — 8의 배수로 맞춘다

  • class P { int x, y; } — → 12 + 4 + 4 = 20 → 패딩으로 24바이트 int 두 개(8바이트)를 담는 데 24바이트를 쓴다

원시 타입 배열이 객체 배열보다 훨씬 조밀한 이유다.

  • int[1000] — 헤더 16 + 4×1000 ≈ 4KB
  • Integer[1000] — 배열 헤더 + 참조 4×1000 + Integer 객체 1000개(각 16B) ≈ 20KB
    • 게다가 객체가 흩어져 있어 캐시 미스가 잦다

String이 특별한 이유

String a = "hi", b = "hi";
a == b;                       // true — 컴파일 시점 상수는 풀에서 공유된다

String c = new String("hi");
c == a;                       // false
c.intern() == a;              // true — 풀의 인스턴스를 얻는다

Java 9부터 String은 내부적으로 byte[] + 인코딩 플래그다(Compact Strings). 라틴 문자만 있으면 문자당 1바이트를 써 메모리를 절반으로 줄인다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 기초·타입·객체지향