Java中的方法内联介绍
1. 什么是方法内联
例如有下面的原始代码:
static class B {
int value;
final int get() {
return value;
}
}
public void foo() {
y = b.get();
// ...do stuff...
z = b.get();
sum = y + z;
}
我们首先要进行的就是方法内联,主要有下面两个目的:
去除方法调用的成本,如查找方法版本、建立栈帧。
为其他优化建立良好基础。
内联后代码如下:
public void foo() {
y = b.value;
// ...do stuff...
z = b.value;
sum = y + z;
}
后续,还可以进行冗余访问消除、复写传播、无用代码消除等优化操作。
2. 方法内联的重要性
方法内联是编译器最重要的优化手段,如果没有内联,多数其他优化都无法有效进行。例如下面这个例子:
public static void foo(Object obj){
if (obj != null) {
System.out.println("do something");
}
}
public static void testInline(String[] args) {
Object obj = null;
foo(obj);
}
testInline()
方法里其实全都是无用的代码,但是如果不做方法内联,就无法发现任何 Dead Code 的存在,因为分开看的话两个方法里面的操作可能都有意义。
3. Java中方法内联的困难
在 JVM 中,只有非虚方法,也就是使用invokespecial
指令调用的私有方法、实例构造器、父类方法和使用invokestatic
指令调用的静态方法才会在编译器进行解析。
而其他虚方法被invokevirtual
指令调用,在调用时必须进行方法接收者的多态选择。对于一个虚方法,编译器静态地去做内联的时候很难确定应该使用哪个方法版本,这就造成了方法内联的困难。
继承类型关系分析 CHA
首先,JVM 引入了一种名为类型继承关系分析 CHA 的技术,这种技术用于在已加载的类中,确定某个接口是否有多于一种的实现、某个类是否存在子类、某个子类是否覆盖了父类的某个虚方法等信息。
编译器在进行内联时会分不同情况采取不同处理:
如果是非虚方法,那么就直接进行内联。
如果是虚方法,那么向 CHA 查询是否有多个目标版本可供选择。
如果只有一个版本,就直接内联,称为守护内联。但由于 Java 程序动态连接,不知道什么时候就会加载到新的类型而改变 CHA 的结论,所以要留好逃生门,假如程序后续执行中加载了导致继承关系发生变化的新类,那么必须抛弃已经编译的代码,退回到解释状态进行执行,或者重新编译。
如果有多个版本可供选择,那即时编译器使用内联缓存来缩减方法调用的开销。内联缓存是一个建立在目标方法正常入口之前的缓存。在未发生方法调用时,内联缓存为空。第一次调用发生后,缓存记录下方法接收者的版本信息,并且在每次进行调用前都检查版本。
如果每次调用的方法接收者版本是一样的,那称为单态内联缓存,通过缓存来调用,相比不内联只多了一次类型判断的开销。如果出现方法接收者不一致的情况,就退化为超多态内联缓存,开销相当于真正查找虚方法表来进行方法分派。当缓存未命中的时候,大多数JVM的实现时退化成超多态内联缓存,也有一些JVM选择重写单态内联缓存,就是更新缓存为新的版本。这样做的好处是以后还可能会命中,坏处是可能白白浪费一个写的开销。
总结
到此这篇关于Java中的方法内联介绍的文章就介绍到这了,更多相关Java方法内联内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341