본문 바로가기
Java

메서드 호출

by AlbertIm 2025. 1. 12.

시작

JVM 내부로 파고들어 메서드 호출이 어떻게 이루어지는지를 정리하고자 합니다. Amazon Corretto 기준으로 작성하였습니다.

본문

invokevirtual

invokevirtual은 가장 일반적인 메서드 호출 방식입니다. 이 방식은 바이트코드를 사용해서 특정 클래스의 객체(또는 하위 클래스)에서 인스턴스 메서드를 호출하는 것을 의미합니다. 이 방식을 또 가상 메서드 디스패치(virtual method dispatch)라고도 합니다. 이 방식은 컴파일 시점에는 호출할 메서드가 정해지지 않고 런타임 시점에 호출할 메서드가 결정됩니다.

 

아래와 같은 코드가 있다고 가정해 봅시다.

abstract class Animal {
    abstract void eat();
}

class Dog extends Animal {

    void eat() {
        System.out.println("Animal is eating");
    }
}

public class Main {

    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.eat();
    }
}

 

위 코드에서 dog.eat()invokevirtual로 호출됩니다. invokevirtual은 가상 메서드 디스패치를 사용하여 메서드를 호출합니다. 즉 컴파일 시점에는 호출할 메서드가 정해지지 않고 런타임 시점에 호출할 메서드가 결정됩니다.

 

INVOKEVIRTUAL me/albert/Dog.eat ()V

 

즉 컴파일 시점에는 호출할 메서드가 정해지지 않고 런타임 시점에 호출할 메서드가 결정됩니다.

 

만약 Kclass의 메서드 테이블에 메서드가 없다면 JVM은 부모 클래스의 메서드 테이블을 참조합니다. 이러한 과정을 통해 메서드 호출이 이루어집니다.

invokevirtual

vtable

내부적으로 위 작업을 수해하기 위해 JVM은 클래스별로 해당 클래스의 메서드 테이블을 가지고 있습니다. 이 테이블은 vtable이라고 불리며 클래스의 메서드들을 가리키는 포인터들을 가지고 있습니다. 이 테이블은 JVM에 필요한 메타데이터를 보관하는 JVM 내부의 메타스페이스(namespace)라는 특수한 메모리 영역에 저장됩니다.

 

모든 자바 객체는 객체 헤더(object header)를 가지고 있습니다. 객체 헤더는 고유한 메타데이터(mark word), 공유하는 메타데이터(klass word)를 가지고 있습니다.

  • mark word: 객체의 상태 정보를 가지고 있습니다. 객체의 상태 정보는 객체가 동기화되었는지, 객체가 가비지 컬렉션의 대상인지 등을 나타냅니다. 힙에 저장된 객체의 메모리 주소를 가지고 있습니다.
  • klass word: 객체의 클래스 정보를 가지고 있습니다. namespace에 저장된 클래스의 메서드 테이블을 가리키는 포인터를 가지고 있습니다.

다시 정리해 봅니다.

  • 스택: 객체의 참조를 가지고 있습니다.
  • 힙: 객체의 인스턴스를 가지고 있습니다. 객체의 인스턴스는 객체 헤더와 인스턴스 데이터를 가지고 있습니다.
  • 메타스페이스: 클래스의 메타데이터를 가지고 있습니다. 클래스의 메타데이터는 클래스의 메서드 테이블을 가지고 있습니다.

invokeInterface

invokeInterface는 인터페이스의 메서드를 호출할 때 사용됩니다. invokeInterfaceinvokevirtual과 비슷하지만 인터페이스의 메서드를 호출할 때 사용됩니다. 컴파일 시점에는 호출할 메서드가 정해지지 않고 런타임 시점에 호출할 메서드가 결정됩니다.

interface Animal {
    void eat();
}

class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.eat();
    }
}

 

위 코드에서 dog.eat()invokeInterface로 호출됩니다. invokeInterface는 인터페이스의 메서드를 호출할 때 사용됩니다. 인터페이스의 메서드를 호출할 때는 인터페이스의 메서드 테이블을 사용합니다.

 

INVOKEINTERFACE me/albert/Animal.eat ()V (itf)

 

즉 dog 참조는 Animal 인터페이스를 가리키고 있기 때문에 Animal 인터페이스의 메서드 테이블을 사용합니다. Animal 인터페이스의 메서드 테이블은 Dog 클래스의 메서드 테이블을 가리키고 있습니다. 만약 dog 참조가 Dog 클래스를 가리키고 있다면 invokevirtual로 호출됩니다.

 

invokeSpecial

invokeSpecialinit 메서드를 호출할 때 사용됩니다.

class Animal {
    void eat() {
        System.out.println("Animal is eating");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.eat();
    }
}

 

위 코드에서 new Animal()invokeSpecial로 호출됩니다.

 

INVOKEVIRTUAL me/albert/Animal.<init> ()V

Java 8에서는 private 메서드를 호출할 때 invokeSpecial을 사용합니다. Java 11부터는 invokeSpecial 대신 invokeVirtual을 사용합니다.

invokeStatic

invokeStatic은 정적 메서드를 호출할 때 사용됩니다.

class Animal {
    static void eat() {
        System.out.println("Animal is eating");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal.eat();
    }
}

 

위 코드에서 Animal.eat()invokeStatic으로 호출됩니다.

 

INVOKESTATIC me/albert/Animal.eat ()V

invokeDynamic

invokeDynamic은 런타임에 동적으로 호출할 메서드를 결정할 때 사용됩니다. invokeDynamicBootstrap 메서드를 사용하여 호출할 메서드를 결정합니다.

Bootstrap 메서드는 CallSite를(unlaced 상태) 생성하고 CallSite는 호출할 메서드를 가리키는 포인터를 가지고 있습니다. 표준 인수는 다음과 같은 타입을 가지고 있습니다.

  • MethodHandles.Lookup: 호출할 메서드를 찾기 위한 메서드 핸들을 가지고 있습니다.
  • String: 호출할 메서드의 이름을 가지고 있습니다.
  • MethodType: 호출할 메서드의 타입을 가지고 있습니다.
public class Main {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("Hello, World!");
        r.run();
    }
}

 

위 코드에서 r.run()invokeDynamic으로 호출됩니다.

 

INVOKEDYNAMIC run()Ljava/lang/Runnable; 

마무리

이 Post에서는 JVM 내부로 파고들어 메서드 호출이 어떻게 이루어지는지를 정리하고자 하였습니다. invokevirtual, invokeInterface, invokeSpecial, invokeStatic, invokeDynamic 등 다양한 메서드 호출 방식을 살펴보았습니다.

참고

'Java' 카테고리의 다른 글

Fork/Join 프레임워크와 CompletableFuture  (0) 2025.01.06
함수형 프로그래밍  (0) 2024.12.31
Gradle  (0) 2024.12.30
메이븐  (0) 2024.12.30
가비지 컬렉션  (1) 2024.12.19

댓글