【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
- 被调用方法版本确定过程
以上通过《深入理解Java虚拟机》学习而来,本人理解上可能存在偏差,如有不当之处欢迎指正,不胜感激。