不知道JVM/javac是不是聪明到可以转
// This line...
string a = foo();
string foo()
{
return bar();
}
string bar()
{
return some-complicated-string computation;
}
进入
string a = bar();
或者在发布情况下去掉对 foo() 的不必要调用(因为无法访问代码):
string a = foo(bar());
// bar is the same
...
string foo(string b)
{
if (debug) do-something-with(b);
}
第一个例子我的感觉是肯定的,第二个例子“不太确定”,但是谁能给我一些指示/链接来确认?
最佳答案
javac
将呈现字节码,该字节码是生成字节码的原始 Java 程序的忠实表示(除了在某些可以优化的情况下:常量折叠和死代码消除)。但是,JVM 在使用 JIT 编译器时可能会执行优化。
对于第一种情况,JVM 似乎支持内联(参见 方法 here 和 here 下的 JVM 内联示例)。
我找不到任何由 javac
本身执行的方法内联示例。我尝试编译一些示例程序(类似于您在问题中描述的那个),但即使是 final
,它们似乎都没有直接内联该方法。这些优化似乎是由 JVM 的 JIT 编译器完成的,而不是由 javac
完成的。 方法中提到的“编译器”here似乎是 HotSpot JVM 的 JIT 编译器,而不是 javac
。
据我所知,javac
支持死代码消除(参见第二种情况的示例)和常量折叠。在常量折叠中,编译器将预先计算常量表达式并使用计算出的值,而不是在运行时执行计算。例如:
public class ConstantFolding {
private static final int a = 100;
private static final int b = 200;
public final void baz() {
int c = a + b;
}
}
编译成以下字节码:
Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private static final int a;
private static final int b;
public ConstantFolding();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public final void baz();
Code:
0: sipush 300
3: istore_1
4: return
}
注意字节码有一个 sipush 300
而不是 aload
的 getfield
s 和一个 iadd
。 300
是计算出来的值。 private final
变量也是如此。如果 a
和 b
不是静态的,则生成的字节码将是:
Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private final int a;
private final int b;
public ConstantFolding();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 100
7: putfield #2; //Field a:I
10: aload_0
11: sipush 200
14: putfield #3; //Field b:I
17: return
public final void baz();
Code:
0: sipush 300
3: istore_1
4: return
}
这里也使用了 sipush 300
。
对于第二种情况(死代码消除),我使用了以下测试程序:
public class InlineTest {
private static final boolean debug = false;
private void baz() {
if(debug) {
String a = foo();
}
}
private String foo() {
return bar();
}
private String bar() {
return "abc";
}
}
给出以下字节码:
Compiled from "InlineTest.java"
public class InlineTest extends java.lang.Object{
private static final boolean debug;
public InlineTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
private void baz();
Code:
0: return
private java.lang.String foo();
Code:
0: aload_0
1: invokespecial #2; //Method bar:()Ljava/lang/String;
4: areturn
private java.lang.String bar();
Code:
0: ldc #3; //String abc
2: areturn
}
如您所见,在 baz
中根本没有调用 foo
,因为 if
block 内的代码实际上是“死的” .
Sun(现在是 Oracle)的 HotSpot JVM 结合了字节码解释和 JIT 编译。当字节码呈现给 JVM 时,代码最初会被解释,但 JVM 会监视字节码并挑选出经常执行的部分。它将这些部分转换为 native 代码,以便它们运行得更快。对于不经常使用的一段字节码,不进行此编译。这也很好,因为编译有一些开销。所以这真的是一个权衡的问题。如果你决定将所有字节码都编译为nativecode,那么代码可能会有很长的启动延迟。
除了监控字节码,JVM 还可以在解释和加载字节码时对字节码进行静态分析,以进行进一步的优化。
如果您想了解 JVM 执行的具体优化类型,this page在甲骨文非常有帮助。它描述了 HotSpot JVM 中使用的性能技术。
关于java - 优化期间会使用 Java 内联方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7772864/