3JVM是如何执行方法调用的
静态绑定:解析时能识别具体的目标方法(重载)
动态绑定:运行过程中根据调用者的动态类型来识别目标方法的情况。
执行时根据实际类型在其方法表中根据索引值获取目标方法。索引值是链接时的指代符号。(重写)
动态绑定与静态绑定相比,仅多出几个内存解引用操作:
- 访问栈上的调用者
- 读取调用者的动态类型
- 读取该类型的方法表
- 读取方法表中某个索引值所对应的目标方法。
调用指令,5种
- invokestatic:用于调用静态方法。
- invokespecial:用于调用私有实例方法、构造器,以及使用 super 关键字调用父类的实例方法或构造器,和所实现接口的默认方法。
- invokevirtual:用于调用非私有实例方法。
- invokeinterface:用于调用接口方法。
- invokedynamic:用于调用动态方法。
即时编译优化手段:内联缓存、方法内联
方法内联:将方法体编译进来取代方法调用,减少了压栈帧,访问,弹出栈帧操作
内联缓存
一种加快动态绑定的优化技术
缓存虚方法调用者类型及其目标方法,以后直接调用该类型对应方法(没有方法内联,还是方法调用),没有缓存则退化为方法表动态绑定。
- 单态内联:只缓存了一种动态类型以及它所对应的目标方法
命中,则直接调用对应的目标方法
为了节省内存空间,Java 虚拟机只采用单态内联缓存
大部分的虚方法调用均是单态的 - 多态内联:
最坏情况,两个实例交替执行调用,每次缓存都不会命中,执行动态链接且每次都会替换缓存。只有写缓存的额外开销,而没有用缓存的性能提升 - 超多态内联:
总结
虚方法调用包括 invokevirtual 指令和 invokeinterface 指令。如果这两种指令所声明的目标方法被标记为 final,那么 Java 虚拟机会采用静态绑定。否则,Java 虚拟机将采用动态绑定,在运行过程中根据调用者的动态类型,来决定具体的目标方法。
Java 虚拟机的动态绑定是通过方法表这一数据结构来实现的。方法表中每一个重写方法的索引值,与父类方法表中被重写的方法的索引值一致。在解析虚方法调用时,Java 虚拟机会纪录下所声明的目标方法的索引值,并且在运行过程中根据这个索引值查找具体的目标方法。
Java 虚拟机中的即时编译器会使用内联缓存来加速动态绑定。Java 虚拟机所采用的单态内联缓存将纪录调用者的动态类型,以及它所对应的目标方法。
当碰到新的调用者时,如果其动态类型与缓存中的类型匹配,则直接调用缓存的目标方法。否则,Java 虚拟机将该内联缓存劣化为超多态内联缓存,在今后的执行过程中直接使用方法表进行动态绑定。