JVM의 5가지 메서드 호출 명령어
vtable 내부 구조 #
들어가기 앞서서 vtable을 먼저 알아보겠다.
invokevirtual 명령어가 다형성을 처리하기 위해 사용하는 jvm 내부 자료구조다.
클래스가 jvm 메모리 영역(메타스페이스)에 로드될 때, 해당 클래스가 실행할 수 있는 모든 가상 메서드들의 실제 메모리 주소를 배열 형태로 매핑하여 vtable을 사용한다.
하위 클래스가 상위 클래스의 메서드를 오버라이딩하면 하위 클래스의 vtable 배열에서 해당 메섣를 가리키는 인덳의 값이 상위 클래스의 메서드 주소에서 하위 클래스가 재정의한 새로운 메서드 주소로 덮어 씌워진다.
메서드 호출 시, JVM은 객체의 헤더를 통해 실제 클래스를 파악하고 해당 클래스의 vtable에 접근한다. 이후 지정된 인덱스(오프셋)을 읽어 실제 실행할 메서드의 메모리 주소로 즉시 점프하여 코드를 실행한다.
5가지 메서드 호출 명령어 #
invokevirtual (가상 메서드 호출) #
클래스의 일반적인 인스턴스 메서드를 호출할 때 사용한다.
자바 애플리케이션에서 가장 빈번하게 발생하는 호출 방식이다. 컴파일 타임이 아닌 런타임 힙영역에 생성된 실제 객체의 타입을 확인한다.
이후 해당 객체의 vtable을 탐색하여 오버라이딩된 실제 메서드의 메모리 주소를 찾아 실행함으로써 객체지향 다형성을 구현한다.
invokespecial (특수 메서드 호출) #
객체의 생성자 <init>, private 메서드나 super를 이용한 부모 클래스 메서드를 호출할 때 사용한다.
다형성이 적용되지 않는 명령어고 호출할 대상 메서드가 컴파일 타임에 정확하게 하나로 고정된다. 런타임에 vtable을 탐색하는 과정이 생략되므로 invokevirtual에 비해 오버헤드 없이 빠르고 직접적으로 메서드가 실행된다.
invokestatic (정적 메서드 호출) #
클래스에 선언된 static 메서드를 호출할 때 사용한다.
특정객체의 인스턴스 상태에 종속되지 않는다. invokespecial과 동일하게 컴파일 시점에 호출 대상 메서드가 확정된다. 인스턴스 생성이나 런타임 타입 확인이 필요 없어지므로 탐색 비용이 발생하지 않아 실행 속도가 매우 빠르다.
invokeinterface (인터페이스 메서드 호출) #
변수의 참조 타입이 클래스가 아닌 인터페이스로 선언되어 있을때 해당 메서드를 호출하기 위해 사용한다.
invokevirtual 처럼 다형성이 적용되어 런타임 실제 구현 객체의 타입을 확인하고 단, 자바에서 클래스는 단일 상속만 가능하지만 인터페이스는 다중 구현이 가능하다.
이로 인해서 메모리상에 메서드 배치되는 규칙이 일정하지 않아 vtable대신 itable을 사용해 탐색 구조가 invokevirtual 보다 조금 더 복잡하게 설계되어있다.
invokedynamic #
자바 8에 도입된 람다 처리와 jvm상에서 구동되는 동적 타입 언어를 지원하기 위해 사용한다.
컴파일 타임에 호출 대상을 확정하지 않는다 대신 프로그램이 실행되는 런타임 시점에
LambdaMetadataFactory와 같은 부트스트랩 메서드를 호출해 대상을 동적으로 결정한다. 이후 메서드 핸들을 통해 호출 지점 call site를 linking한다. 람다식 사용시 무의미한 익명 클래스 파일 생성을 방지하여 메모리 낭비를 줄이고 실행 유연성을 제공한다.
itable #
명령어가 실제 실행할 구현체의 메서드 메모리 주소를 찾을 때 사용하는 JVM 내부 자료구조
vtable과의 차이점은 자바의 클래스는 단일 상속만 허용하므로 vtable에는 고정된 인덱스(오프셋)을 부여해 배열처럼 즉시 메서드에 접근할 수 있다.
반면, 인터페이스는 다중 구현이 가능하기 때문에 클래스마다 어떤 인터페이스들을 조합하여 구현할지 알 수 없다. 따라서 전체 메모리상에 고정된 공통 인덱스를 부여할 수 없어, 보다 유연한 구조의 itable을 사용한다.
내부 구조 및 탐색 원리 #
itable은 해당 클래스가 구현한 인터페이스들의 오프셋 정보 offset table와 각 인터페이스에 속한 메서드들의 실제 메모리 주소 두 부분으로 구성된다.
- 인터페이스 검색: 메서드 호출시 객체 헤더를 통해 클래스
itable로 접근한다. 우선 호출하려는 인터페이스 자체가 테이블 내에 존재하는지 순차적 혹은 이진으로 검색한 다. 이 검색과정때문에 vtable의 단순 배열 접근보다 미세하게 느리다. - 메서드 주소 확인: 일치하는 인터페이스 블록을 찾으면, 그 안에서 호출하려는 특정 메서드의 위치 (오프셋) 을 확인하여 실제 메모리 주소를 알아낸다.
- 점프 및 실행: 최종적으로 확보한 주소로 점프하여 구현된 코드를 실행한다.
이러한 itable의 탐색 오버헤드를 극복하기 위해 JIT이 런타임에 반복되는 호출을 최적화하기위해 인라인 캐싱을 해주는거다.