虚方法
invokeinterface,invokevirtual:JVM中虚方法调用
虚方法更耗时,动态绑定
静态绑定:invokestatic,invokespecial
如果虚方法指向final方法,那么JVM可以静态绑定此虚方法调用的目标方法
JVM:空间换时间策略实现动态绑定,方法表
方法表
本质:数组,元素指向一个当前类及祖先类中非私有的实例方法
这些方法可能具体,可能是没有相应字节码的抽象方法
2个特质:
- 子类方法表包含父类方法表中所有方法
- 子类方法在方法表的索引值与重写父类方法表中的索引值相同
动态绑定和静态绑定相比,仅仅多了几个内存解引用操作:访问栈上的调用者,读取调用者的动态类型,读取此类型方法表中某个索引值对应目标方法
虚引用调用对性能的影响
上述优化效果仅存在于解释之星,或者即时编译代码的最坏情况,即时编译还有2种性能更好的优化手段:内联缓存,方法内联
内联缓存
- 加快动态绑定的优化技术
- 缓存虚方法调用中调用者的动态类型,以及此类型对应的目标方法
- 执行过程中碰到已缓存的类型,内联缓存直接调用此类型对应目标方法,没有碰到内联缓存退化至使用基于方法表的动态绑定 先问是不是中国人,是就左边通道,不是就拿出外国人小册子,翻到第一页,然后告知结果:右边
针对多态的优化手段:
- 单态:一种状态
- 多态:有限数量种状态
- 超多态:更多种状态,通常用一个具体数值区分多态和超多态
- 单态内联缓存:只缓存了一种动态类型及目标方法,实现:比较缓存的动态类型,命中直接调用目标方法
- 多态内联缓存:多个,逐个将缓存的动态类型比较,命中则调用
- 超多态内联缓存:
大部分虚拟机是单态,为了节省内存空间,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