JVM基本原理:JVM如何执行方法调用?——续集

117 阅读4分钟

虚方法

invokeinterface,invokevirtual:JVM中虚方法调用

虚方法更耗时,动态绑定

静态绑定:invokestatic,invokespecial

如果虚方法指向final方法,那么JVM可以静态绑定此虚方法调用的目标方法

JVM:空间换时间策略实现动态绑定,方法表

方法表

本质:数组,元素指向一个当前类及祖先类中非私有的实例方法

这些方法可能具体,可能是没有相应字节码的抽象方法

2个特质:

  1. 子类方法表包含父类方法表中所有方法
  2. 子类方法在方法表的索引值与重写父类方法表中的索引值相同

动态绑定和静态绑定相比,仅仅多了几个内存解引用操作:访问栈上的调用者,读取调用者的动态类型,读取此类型方法表中某个索引值对应目标方法

虚引用调用对性能的影响

上述优化效果仅存在于解释之星,或者即时编译代码的最坏情况,即时编译还有2种性能更好的优化手段:内联缓存,方法内联

内联缓存

  • 加快动态绑定的优化技术
  • 缓存虚方法调用中调用者的动态类型,以及此类型对应的目标方法
  • 执行过程中碰到已缓存的类型,内联缓存直接调用此类型对应目标方法,没有碰到内联缓存退化至使用基于方法表的动态绑定 先问是不是中国人,是就左边通道,不是就拿出外国人小册子,翻到第一页,然后告知结果:右边

针对多态的优化手段:

  1. 单态:一种状态
  2. 多态:有限数量种状态
  3. 超多态:更多种状态,通常用一个具体数值区分多态和超多态
  • 单态内联缓存:只缓存了一种动态类型及目标方法,实现:比较缓存的动态类型,命中直接调用目标方法
  • 多态内联缓存:多个,逐个将缓存的动态类型比较,命中则调用
  • 超多态内联缓存:

大部分虚拟机是单态,为了节省内存空间,JVM只采用单态

将更加热门的动态类型放在前面

  • 没有命中,对于内联缓存中内容,2种选择

    • 替换单态内联缓存中的记录,在替换内联缓存之后一段时间方法调用者的动态类型应保持一致,从而有效利用内联缓存

      • 最坏情况我们用两种不同类型调用者,轮流执行该方法调用,每次调用都替换内联缓存,只有写缓存的额外开销,没有用缓存的性能提升
    • 劣化为超多态内联缓存:放弃优化,直接访问方法表动态绑定目标对象,节省了写缓存的额外开销

“Java的动态类型运行期才可知”,在编译期代码写完之后应该就已经确定了吧,比如A是B的子类,“B b = new B(); b= new A()”这种情况下b的动态类型是A,Java编译器在编译阶段就可以确定啊,为什么说动态类型直到运行期才可知?

那你看我下面这段伪代码,告诉我如何在编译器给我确定A的动态类型

A a = null; 
int i = 随机数; 
if(i  是奇数){ 
    a = new B(); 
}else{   
    a = new C(); 
} 
a.fun();

单态内联缓存和超多态内联缓存性能差异

java -XX:CompileCommand='dontinline,*.passThrouguImmigration' Passenger
    inline
abstract class Passenger {
    abstract void passThroughImmigration();
​
    public static void main(String[] args) {
        Passenger a = new ChinesePassenger();
        Passenger b = new ForeignerPassenger();
        long current = System.currentTimeMillis();
​
        for (int i = 1; i < 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }
            Passenger c = (i < 1_000_000_000) ? a : b;
            c.passThroughImmigration();
        }
    }
}
​
class ChinesePassenger extends Passenger {
​
    @Override
    void passThroughImmigration() {
    }
}
​
class ForeignerPassenger extends Passenger {
​
    @Override
    void passThroughImmigration() {
    }
}
WIN10 JDK 8的运行结果如下:
D:\Workspace\Ucar\demo\src\main\java>java -XX:CompileCommand=dontinline,*.passThroughImmigration Passenger
CompilerOracle: dontinline *.passThroughImmigration
282
288
280
314
271
284
290
332
294
285
359
334
336
385
332
364
328
331
380
336
​
D:\Workspace\Ucar\demo\src\main\java>java -XX:CompileCommand=inline,*.passThroughImmigration Passenger
CompilerOracle: inline *.passThroughImmigration
103
128
191
150
156
153
124
124
127
177
183
190
191
169
234
178
155
162
169
176