Java中的内联化

40

在C++中,我可以声明一个方法为"inline",编译器可能会将其内联。据我所知,在Java中没有这样的关键字。

如果JVM决定这样做,就会进行内联处理吗?我能以某种方式影响这个决定吗?

8个回答

54

其他答案中有一些人提到只有final方法才能被内联,但这是不正确的。HotSpot聪明到可以内联非final方法,只要它们还没有被覆盖。

当加载一个覆盖该方法的类时,它会取消内联优化。显然,将该方法变成final意味着永远不需要执行此操作...

基本上让JVM完成其工作 - 它很可能比您更擅长在哪里进行内联。

您是否遇到了JVM无法很好地运行的情况?假设您正在使用HotSpot,您是否已尝试使用服务器版本而不是客户端版本?这可能会产生巨大的差异。


30
实际上,即使方法被覆盖,HotSpot也可以(推测性地)内联方法,并对不符合期望类型的对象进行虚拟方法调用。Server HotSpot甚至可以内联两个不同版本的方法(双态内联)。 - Tom Hawtin - tackline
3
@Tom:你介意我把那个包含在答案里吗? - Jon Skeet
3
有些情况下,热点代码没有被内联,而手动内联可以将核心处理循环的性能提升5倍。 - R.Moeller

6
虽然Java编译器可以进行内联(对于短的早期绑定方法),但是真正的内联将由JIT编译器完成。 JIT(HotSpot)编译器甚至可以内联虚拟方法。 与其交互的最佳方式是编写简单明了的代码。 使用反射的代码很可能不允许内联。
希望这有所帮助。

1
你能提供任何证据表明Java编译器(而不是JIT编译器)内联方法调用吗? - Michael Borgwardt
“很可能使用反射的代码不允许内联” - 这篇帖子似乎是相反的,是吗? - MC Emperor

5
在C++中,我可以声明一个方法为"inline",编译器会将其内联...或者不会。编译器可以自由选择将函数内联或不内联,你无法真正影响结果。这只是对编译器的提示。
在Java中没有这样的东西,编译器(以及后来进行优化时的VM)可以决定将方法“内联”。
请注意,“final”方法更有可能被内联(编译器无法内联非final方法,因为它们可能在派生类中被覆盖)。使用现代VM,类似的优化可以在运行时进行。VM将标记类型(以便进行类型检查),并内联代码。仅当检查失败时,它才会回退到原始未优化的多态方法调用。

2
重要提示!在C语言中,“inline”并不会强制编译器将你的方法内联。 - Joachim Sauer
谢谢,你说得对,我忘记了“inline”只是给编译器的建议! - CL23
有时候你可以强制编译器进行内联,但这取决于编译器:https://dev59.com/5HNA5IYBdhLWcg3wgeEd - Liran Orevi
我非常确定Java编译器无法决定内联方法 - 只有JIT编译器可以。 - Michael Borgwardt
2
编译器无法内联非final方法,因为它们可能会在派生类中被重写)- 这是完全错误的说法。如果在运行时尚未被覆盖,JIT编译器可以内联非final方法。 - Steve Kuo
@SteveKuo:这就是为什么final方法有更大的机会的原因,也许在答案中没有表述清楚。 - David Rodríguez - dribeas

4

如果涉及的方法:

  • 短小精悍
  • 是最终版本
  • 不依赖于任何长的非最终方法

在这些情况下,虚拟机更有可能进行内联操作。因为只有在这种情况下JVM才能确定调用的效果。


3
final 对于 HotSpot 没有任何影响。 - Tom Hawtin - tackline
@TomHawtin-tackline 有趣,你能详细说明一下吗? - MC Emperor
@MCEmperor 可能会有所更改,但它只是检查是否有任何已加载的类覆盖了该方法。 - Tom Hawtin - tackline

3
class A {
    final int foo() { return 3; }
}

给定这个类,任何对foo()的调用都可以被替换为常量“3”。任何Java1虚拟机都可以做到这一点,因为“final”关键字明确规定了不可能有一个子类覆盖“int foo()”。
在调用站点进行方法内联提供以下好处:
- 没有方法调用 - 没有动态分派 - 可能将值常量折叠,例如,“a.foo()+2”变成5,没有在运行时执行任何代码。
过去,程序员经常插入“final”关键字,正是出于这个原因。或者为了更好地促进内联并增加执行速度,他们会将许多较小的方法合并为一个较大的方法。但在许多方面,这些技术都破坏了编程语言中构建的模块化和可重用性的整个设施。
现代JVM,如Java HotSpot VM,能够在没有“final”关键字的情况下内联该类。

(http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html)


为了提高可读性,有时候我们会将某些计算过程拆分到多个函数中。但同时,为了避免大量函数调用的开销,我们希望这些调用在运行时内联执行。因此,使用一个关键字(向编译器发出建议)来使函数调用成为内联调用更有意义。 - mawia
下面@David Rodriguez的回答很有道理。如果inline关键字仅仅是给编译器一个提示,那么最好让编译器自己决定是否进行内联调用,相信它会在可能的情况下进行内联。 - mawia

2

1
该文章暗示解释已成为常态 - "在撰写本文时,出现了即时编译器",这表明它已经过时。虚拟机已经发展了很长一段时间。 - Jon Skeet

1

是的,如果JVM决定这样做,它可以进行内联优化。影响方式包括将方法设置为静态或final。

当然,最重要的是该方法的结构需要对内联友好。短小有助于内联优化,但更重要的是它只能使用局部变量和参数,不能使用字段,并且对同一类中的其他方法的调用应尽量减少。

然而,您不应过早地进行此类优化,因为您实际上可能会使情况变得更糟(因为您可能会绕过其他潜在的优化)。JVM有时会意识到无需这些提示就可以内联方法。


1

当比较普通函数和最终函数(JVM称之为内联函数)时,我发现它们之间没有性能提升。也许函数调用的开销已经非常低了。

注意:我使用盒状模糊算法来评估性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接