方法的调用:虚方法与非虚方法
4种方法调用指令
-
虚拟机中提供了以下几条方法调用指令:
-
普通调用指令:
- invokestatic: 调用静态方法,解析阶段唯一确定唯一方法版本
- invokespecial: 调用
方法、私有及父类方法,解析阶段确定唯一方法版本 - invokevirtual: 调用所有虚方法
- invokeinterface: 调用接口方法
-
动态调用指令:
5. invokedynamic: 动态解析处需要调用的方法,然后执行。
-
-
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而 invokedynamic 指令则支持由用户确定方法版本,其中 invokestatic 指令和 Invokespecial 指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。
方法的调用:关于 invokedynamic 指令
- JVM 字节码指令集一直比较稳定,一直到 Java7 中才增加了一个 invokedynamic 指令,这是Java 为了实现「动态类型语言」支持而做的一种改进。
- 但是在 Java7 中并没有提供直接生成 invokedynamic 指令的方法,需要借助 ASM 这种底层字节码工具来产生 invokedynamic 指令。直到Java8 的 Lambda 表达式的出现,invokedynamic 指令的生成,在 Java 中才有了直接的生成方式。
- Java7 中增加的动态语言类型支持的本质式对 Java 虚拟机规范的修改,而不是对 Java 语言规则的修改,这一块相对来说比较复杂,增加了虚拟机中的方法调用,最直接的受益者就是运行在 Java 平台的动态语言的编译器。
动态类型语言和静态类型语言
- 动态类型语言和静态类型语言两者的区别在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之就是动态类型语言。
- 说的再直白一点就是,**静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息。**这是动态语言的一个重要特征。
方法的调用:方法重写的本质
Java 语言中方法重写的本质
- 找到操作数栈顶的第一个元素所执行的对象的实际类型,记作 C 。
- 如果在类型 C 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回 java.lang.IllegalAccessError 异常。
- 否则,按照继承关系从下往上一次对 C 的各个父类进行第 2 步的搜索与验证过程。
- 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError 异常。
IllegalAccessError 介绍:
- 程序试图访问或修改一个属性或调用一个方法,这个属性或方法,你没有权限访问。一般的,这个会引起编译器异常。这个错误如果发生在运行时,就说明一个类发生了不兼容的改变。
方法的调用:虚方法表
- 在面向对象的编程中,会很频繁的使用动态分配,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话就可能影响到执行效率。因此,为了提高性能,JVM 采用在类的方法区建立一个虚方法表( virtual method table )(非虚方法不会出现在表中)来实现。使用索引表来代替查找。
- 每个类中都有一个虚方法表,表中存放着各个方法的实际入口。
- 那么虚方法表是什么时候被创建?
- 虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM 会把该类的方法表也初始化完毕。