자바 언어·플랫폼 용어 사전
JVMinvokevirtual · invokestatic · invokeinterface · vtable · 동적 바인딩

메서드 디스패치

어떤 메서드 구현을 실제로 부를지 정하는 절차. 정적 바인딩과 동적 바인딩이 갈린다.

어느 구현을 실제로 부를지 정하는 절차. 바이트코드 명령이 방식을 결정한다.

  • invokestatic — static 메서드 — 컴파일 타임에 확정
  • invokespecial — 생성자 · private · super 호출 — 확정
  • invokevirtual — 일반 인스턴스 메서드 — 런타임 실제 타입으로 결정
  • invokeinterface — 인터페이스 메서드 — 런타임 결정, 탐색이 조금 더 복잡
  • invokedynamic — 호출 지점을 런타임에 연결 — 람다 · 문자열 결합

오버로딩과 오버라이딩이 갈리는 지점

void f(Object o) { print("Object"); }
void f(String s) { print("String"); }

Object x = "hi";
f(x);            // "Object" — 오버로딩은 컴파일 타임 정적 타입으로 고른다
Parent p = new Child();
p.hello();       // Child 의 구현 — 오버라이딩은 런타임 실제 타입
  • 오버로딩 — '어느 시그니처인가' → 정적 타입 (컴파일 타임)

  • 오버라이딩 '어느 구현인가' — → 실제 타입 (런타임)

  • 둘을 섞으면 직관을 배신한다 — 오버로딩은 되도록 시그니처를 확실히 구분한다

vtable 과 인라인 캐시

각 클래스는 메서드 테이블(vtable) 을 갖는다 invokevirtual 은 객체 헤더 → 클래스 → vtable 슬롯을 따라간다

그런데 실제로는 대부분 한 종류만 온다
→ JIT 이 호출 지점에서 관찰한다
   단형(monomorphic)   구현이 하나뿐 → 인라인해 버린다 (사실상 공짜)
   이형(bimorphic)     둘 → 분기 후 인라인
   다형(megamorphic)   셋 이상 → 인라인 포기, 진짜 vtable 조회

"인터페이스가 느리다" 가 대체로 사실이 아닌 이유이자, 구현체를 무분별하게 늘리면 실제로 느려질 수 있는 이유다

private·static 이 빠른 이유

오버라이딩될 수 없으므로 대상이 확정된다 — 조회도 추측도 필요 없고 인라인도 쉽다.

인라인이 최적화의 관문인 이유

인라인 자체의 이득(호출 비용 제거) 은 사실 작다 진짜 이득은 그다음이다

  • 인라인 → 두 메서드가 한 덩어리가 된다
    • 상수 전파 · 죽은 코드 제거 · 이스케이프 분석이 가능해진다
    • 객체가 아예 안 만들어지기도 한다 (스칼라 치환)

그래서 인라인이 막히면 뒤따르는 최적화가 통째로 사라진다 막히는 이유

  • 메서드가 너무 크다 (-XX:MaxInlineSize · FreqInlineSize)
  • 호출 지점이 다형(megamorphic)
  • 호출 대상이 런타임까지 확정 안 됨

CHA 와 역최적화

클래스 계층 분석(CHA): 현재 로딩된 클래스 중 구현이 하나뿐이면 JIT 은 가상 호출을 확정 호출로 바꿔 인라인한다

나중에 두 번째 구현이 로딩되면?

  • 그 가정이 깨진다 → 컴파일된 코드를 폐기하고 인터프리터로 되돌린다
    • (deoptimization) 후 다시 컴파일

인터페이스에 구현이 하나뿐인 코드가 빠른 이유이자, 런타임에 구현체가 늘어나면 성능이 변할 수 있는 이유다

면접 함정

  • "오버로딩도 런타임에 정해진다" → 컴파일 타임이다.
  • "가상 호출은 항상 비싸다" → 단형 호출은 JIT이 인라인해 없앤다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JVM 실행 흐름 — 내 코드가 도는 과정