编译器(或 JVM)是​​否知道优化这段代码?我该如何检查?

One*_*ree 3 java compiler-construction compiler-optimization

假设我有这门课

Util
{
    public static void doSomething()
    {
         if (FLAG) foo();
         else bar();
    }

    public static void foo() { /* do something */ }
    public static void bar() { /* do something else */ }

    public static final boolean FLAG = computeFlag();
    private static boolean computeFlag() { /* do some computation during init time*/ }
}
Run Code Online (Sandbox Code Playgroud)

FLAG显然永远不会改变。假设Util.doSomething()大量使用(并且在许多关键的地方,性能确实很重要)。Java 编译器或 JVM 是否足够聪明,可以缓存主体,doSomething以便代码不必重新计算FLAG或重新执行分支指令?

我该如何检查这个?

谢谢

mer*_*ike 5

这可能取决于您使用的 JVM。对于 Oracle Hotspot JVM,您可以使用以下命令检查生成的机器代码

java -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly your.MainClass
Run Code Online (Sandbox Code Playgroud)

前提是您在库路径中有必要的本机库(文档提到您可以在哪里获取此类二进制文件)。

运行班级:

public class DecompileTest {

    public static void doSomething() {
        if (FLAG)
            foo();
        else
            bar();
    }

    static int fooCount;

    public static void foo() {
        fooCount++;
    }

    public static void bar() {
        fooCount--;
    }

    public static final boolean FLAG = computeFlag();

    private static boolean computeFlag() {
        System.out.println("Shall I set the flag? (y/n)");
        try {
            return System.in.read() == 'y';
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            doSomething();
        }
        System.out.println(fooCount);
    }   
}
Run Code Online (Sandbox Code Playgroud)

java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) Server VM (build 23.21-b01, mixed mode)
Run Code Online (Sandbox Code Playgroud)

在我的 Intel cpu 上会产生冗长的 disassabmly,其中相关部​​分如下:

Decoding compiled method 0x009ca408:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} 'doSomething' '()V' in 'stackoverflow/DecompileTest'
  #           [sp+0x10]  (sp of caller)
  0x009ca500: sub    $0xc,%esp
  0x009ca506: mov    %ebp,0x8(%esp)     ;*synchronization entry
                                        ; - stackoverflow.DecompileTest::doSomething@-1 (line 8)
  0x009ca50a: mov    $0x295dc208,%ebx   ;   {oop(a 'java/lang/Class' = 'stackoverflow/DecompileTest')}
  0x009ca50f: incl   0x70(%ebx)         ;*putstatic fooCount
                                        ; - stackoverflow.DecompileTest::foo@5 (line 17)
                                        ; - stackoverflow.DecompileTest::doSomething@6 (line 9)
  0x009ca512: add    $0x8,%esp
  0x009ca515: pop    %ebp
  0x009ca516: test   %eax,0x950000      ;   {poll_return}
  0x009ca51c: ret    
  0x009ca51d: hlt    
  0x009ca51e: hlt    
  0x009ca51f: hlt    
[Exception Handler]
[Stub Code]
  0x009ca520: jmp    0x009c78c0         ;   {no_reloc}
[Deopt Handler Code]
  0x009ca525: push   $0x9ca525          ;   {section_word}
  0x009ca52a: jmp    0x009ae280         ;   {runtime_call}
  0x009ca52f: hlt    
Run Code Online (Sandbox Code Playgroud)

也就是说, 的测试FLAG和调用都已bar()作为死代码被消除,并且foo内联的方法体。