【JVM学习笔记】如何确定被调用方法的版本

在字节码层面,Java方法的调用设计到如下几个字节码:

  • invokestatic #index
    调用静态方法,被调用方法的版本可在类加载解析阶段确定,并将符号引用转换为直接引用
  • invokespecial #index
    调用实例构造器,私有方法,父类方法,被调用方法的版本可在类加载解析阶段确定,并将符号引用转换为直接引用
  • invokevirtual #index
    调用final方法的版本确定时期同以上两种
    其他被调用版本需要待运行期才能完全确定:
    • 1.找到操作数栈顶第一个元素所执行的对象的实际类型C(确定接收者的实际类型)
    • 2.如果在C中找到与常量描述符这简单名称均相对的方法,则进去访问权限校验,通过则返回方法的直接引用,否则抛IllegalAccessError
    • 3.否则,按照继承关系,从下往上以此对C的各个父类进行第2步骤的搜索和验证过程
package com.cong.invoke_method;

class Task {

    private String taskName;

    public Task() {}

    public Task(String taskName) {
        this.taskName = taskName;
    }

    public String getTaskName() {
        return this.taskName;
    }

}

class HomeWork extends Task {

    public HomeWork() {}

    public HomeWork(String taskName) {
        super(taskName);
    }

}

class People {

    private Integer age;

    public static void description() {
        System.out.println("I'm people, earth is our home");
    }

    public People() {}

    public People(Integer age) {
        this.age = age;
    }

    public Integer getAge() {return this.age;}

    public void doTask(Task task) {
        System.out.println("Do task : " + task.getTaskName());
    }

    public void doTask(HomeWork homeWork) {
        System.out.println("Do homework : " + homeWork.getTaskName());
    }

    public final void sayPeople() {
        System.out.println("we're come from earth, It's very beautiful");
    }

}

class Student extends People {

    private Integer studentId;

    public Student() {}

    public Student(Integer studentId, Integer age) {
        /**
         * invokespecial #2    // Method com/cong/invoke_method/People."<init>":(Ljava/lang/Integer;)V
         * 调用父类实例构造器,类加载期间可确定方法版本
         */
        super(age);
        this.studentId = studentId;
    }

    private void sayMyself() {
        /**
         * invokespecial #11    // Method com/cong/invoke_method/People.getAge:()Ljava/lang/Integer;
         * 调用父类函数,类加载期间可确定方法版本
         */
        System.out.println("My studentId is :" + studentId + ",and my age is :" + super.getAge());
    }

    @Override
    public void doTask(Task task) {
        /**
         * invokespecial #14    // Method sayMyself:()V
         * 调用私有方法,类加载期间可确定方法版本
         */
        this.sayMyself();
        System.out.println("Student do task : " + task.getTaskName());
    }
}

public class MainClass {

    public static void main(String[] args) {
        /**
         * invokestatic  #2    // Method com/cong/invoke_method/People.description:()V
         * 调用类静态方法,类加载期间可确定方法版本
         */
        People.description();

        /**
         * invokespecial #5    // Method com/cong/invoke_method/Student."<init>":(Ljava/lang/Integer;Ljava/lang/Integer;)V
         * 调用实例构造器,类加载期间可确定方法版本
         */
        People student = new Student(10001, 12);

        Task task = new Task("任务:帮妈妈打扫卫生");

        /**
         * invokevirtual #9    // Method com/cong/invoke_method/People.doTask:(Lcom/cong/invoke_method/Task;)V
         * 类加载期间,根据task的静态类型确定版本
         * 运行期间,方法的接受者为Student类型,调用Student的doTask方法;
         */
        student.doTask(task);

        Task homework1 = new HomeWork("作业:数学");    //invokespecial #index,调用实例构造器,类加载期间可确定方法版本

        /**
         * invokevirtual #9    // Method com/cong/invoke_method/People.doTask:(Lcom/cong/invoke_method/Task;)V
         * 类加载期间,根据task的静态类型确定版本
         * 运行期间,方法的接受者为Student类型,调用Student的doTask方法;
         */
        student.doTask(homework1);

        /**
         * invokevirtual #14    // Method com/cong/invoke_method/People.doTask:(Lcom/cong/invoke_method/HomeWork;)V
         * 类加载期间,根据task的静态类型确定版本
         * 运行期间,方法的接受者为Student类型,调用Student的doTask方法(没有重写,从父类继承而来);
         */
        student.doTask(new HomeWork("作业:语文"));

        People people = new People(12);

        /**
         * invokevirtual #17    // Method com/cong/invoke_method/People.sayPeople:()V
         * 调用final方法,类加载期间可确定方法版本
         */
        people.sayPeople();

        /**
         * invokevirtual #9    // Method com/cong/invoke_method/People.doTask:(Lcom/cong/invoke_method/Task;)V
         * 类加载期间,根据task的静态类型确定版本
         * 运行期间,方法的接受者为People类型,调用People的doTask方法;
         */
        people.doTask(new Task("任务:写代码"));
    }

}

总结一下

  • 分派分类
People student = new Student();//student的静态类型为People,实际类型为Student
 - 静态分派:依据对象的静态类型——Overload
 - 动态分派:依据对象的实际类型——Override
  • 被调用方法版本确定过程
    【JVM学习笔记】如何确定被调用方法的版本
    以上通过《深入理解Java虚拟机》学习而来,本人理解上可能存在偏差,如有不当之处欢迎指正,不胜感激。