5

Java final 方法会自动内联吗?

很多书说是,很多书说不是!!!

4

7 回答 7

10

方法的内联由 JIT 编译器执行,而不是 javac。

现代 JIT 编译器(包括 Hotspot)通常可以内联甚至非最终方法,必要时适当地“撤消”优化。他们基本上非常聪明。

简而言之:它完全取决于虚拟机。在我看来,你应该根据产生最干净的代码而不是性能来确定你的方法是否是最终的。我个人是“为继承而设计或禁止它”的粉丝,但这是一个不同的讨论:)

于 2009-11-09T13:23:09.880 回答
4

有趣的问题,促使我进一步研究。我发现了 2 条有趣的评论——

  • 1条评论指出自动内联是一个错误:

与许多提示的含义相反,声明为 final 的方法不能被编译器安全地内联,因为该方法在运行时可能具有非 final 声明。

要了解原因,假设编译器查看类 A 和子类 B,以及子子类 C,并在 A 中看到它内联到 C 中的最终方法。但是在运行时为 A 和 B 加载的版本不同,方法是在 A 中不是最终版本,在 B 中被覆盖。然后 C 使用不正确的内联版本。吨

而且,更具权威性的是,来自sun 的白皮书,写到方法可以是虚拟的,

因为 Java HotSpot VM 可以自动内联绝大多数虚拟方法调用,所以这种性能损失显着降低,并且在许多情况下完全消除。

这是关于机制的更直接的参考

于 2009-11-09T13:41:58.887 回答
3

如果您的意思是“他们在编译期间是否内联”,那么不,他们不会。

但是,静态最终字段有时可以由编译器内联,例如原语和字符串。

于 2009-11-09T13:19:39.733 回答
2

final更多的是为设计添加语义,而不是告诉编译器或 VM 内联某些东西。现代 VM 内联的内容远比单独的 final 方法多,因此这不是使用final或尝试过多预测运行时优化的好理由。

于 2009-11-09T13:26:50.640 回答
1

我认为这取决于您运行的 JVM 的实现。当然,使方法最终允许编译器选择进行此类实现调整。但它是否也可能取决于其他因素 - 例如,如果它是一个巨大的方法,等等......

于 2009-11-09T13:21:34.317 回答
0

Hotspot 是否内联的决定非常复杂,取决于过多的考虑因素,但我不认为该方法是否标记为“最终”是其中之一。原因是它已经知道该方法的多个实现是否已加载到 VM 中,因此是否也知道是否允许此类实现是无关紧要的。

无论如何,它只会是非常小且简单的内联方法,甚至不是所有这些方法。

于 2009-11-09T20:38:00.310 回答
0

正如 Jon 所说,内联是由 JIT 编译器完成的(在需要时),而不是在字节码生成级别。另请注意,有时内联会导致性能下降,因为它可能会造成相同代码多次出现在 cpu l1 缓存中的情况,从而为其他代码腾出空间。与跳转到缓存函数相比,L1 缓存未命中对性能的影响更大。

常量(又名最终静态变量)改为内联。

看到这个来检查

public class InlineTest {
    final static int add(int x, int y) {
        return x + y;
    } 
}


public class Main {

        static final int DIVISOR = 7;

        static void main(String[] args){
            final int a = new Integer(args[0]);
            final int b = new Integer(args[1]);

            if (InlineTest.add(a, b) % DIVISOR == 0)
                System.exit(InlineTest.add(a, b));

            System.out.print("The sum is " + InlineTest.add(a, b));

        }
}

这被翻译成:

 0 new #2 <java/lang/Integer>
 3 dup
 4 aload_0
 5 iconst_0
 6 aaload
 7 invokespecial #3 <java/lang/Integer/<init>(Ljava/lang/String;)V>
10 invokevirtual #4 <java/lang/Integer/intValue()I>
13 istore_1
14 new #2 <java/lang/Integer>
17 dup
18 aload_0
19 iconst_1
20 aaload
21 invokespecial #3 <java/lang/Integer/<init>(Ljava/lang/String;)V>
24 invokevirtual #4 <java/lang/Integer/intValue()I>
27 istore_2
28 iload_1
29 iload_2
30 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
33 bipush 7
35 irem
36 ifne 47 (+11)
39 iload_1
40 iload_2
41 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
44 invokestatic #7 <java/lang/System/exit(I)V>
47 getstatic #8 <java/lang/System/out Ljava/io/PrintStream;>
50 new #9 <java/lang/StringBuilder>
53 dup
54 invokespecial #10 <java/lang/StringBuilder/<init>()V>
57 ldc #11 <The sum is >
59 invokevirtual #12 <java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;>
62 iload_1
63 iload_2
64 invokestatic #5 <com/gamasoft/InlineTest/add(II)I>
67 invokevirtual #13 <java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;>
70 invokevirtual #14 <java/lang/StringBuilder/toString()Ljava/lang/String;>
73 invokevirtual #15 <java/io/PrintStream/print(Ljava/lang/String;)V>
76 return

您可以看到静态函数 InlineTest.add 已被多次调用invokestatic

于 2013-03-01T10:39:35.920 回答