虚方法和非虚方法
非虚方法:方法在编译器就确定了具体的调用版本,这个版本的运行时是不可变的(非虚方法有静态方法、私有方法、final方法、实例构造器、父类方法,其他方法为虚方法)。
jvm提供了以下几条方法调用指令:
- 调用非虚方法(final修饰的方法除外):
- (1)invokestatic:调用静态方法,解析阶段确定唯一方法版本。
- (2)invokespecial:调用
方法、私有方法、父类方法,解析阶段确定唯一方法版本。
- 调用虚方法:
- (3)invokevirtual:调用所有虚方法。(调用final方法用的是这个指令)
- (4)invokeinterface:调用接口方法。
- (5)invokedynamic:动态解析出需要调用的方法,然后执行。
- 调用非虚方法(final修饰的方法除外):
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令由用户确定方法版本,其中invokestatic和invokespecial指令调用的方法为非虚方法,其余的(final修饰的方法除外)为虚方法。
例子:
1 | public class Father { |
由编译后的字节码文件可得证:
1 | 0 invokestatic #8 <com/jvm/demo/Son.showStatic> |
动态和静态类型语言的区别在于对类型的检查是编译期还是运行期,满足前者为静态类型,满足后者为动态类型(即静态类型语言是判断变量自身的类型信息,动态类型是判断变量值的类型信息)。
java语言中方法重写的本质:
- ①找到操作数栈顶的第一个元素所执行的对象的实际类型,记做C。
- ②如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
- ③否则,按照继承关系从下往上依次对C的各个父类进行第②步的搜索和验证过程。
- ④如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError的异常。
为了提高性能,JVM采用在类的方法区建立一个虚方法表,使用索引表来代替查找。即每个类都有一个虚方法表,表中存放着各个方法的实际入口。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class VirtualMethodTable {
}
interface Friendly {
void sayHello();
void sayGoodbye();
}
class Dog {
public void sayHello() {
}
public String toString() {
return "Dog";
}
}
class Cat implements Friendly {
public void eat() {
}
public void sayHello() {
}
public void sayGoodbye() {
}
protected void finalize() {
}
public String toString(){
return "Cat";
}
}
class CockerSpaniel extends Dog implements Friendly {
public void sayHello() {
super.sayHello();
}
public void sayGoodbye() {
}
}则Dog类的虚方法表:
CockerSpaniel表的虚方法表:
Cat类的虚方法表:
- 虚方法表什么时候被创建?
- 在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM会把该类的方法表也初始化完毕。