在优化过程中Java内联方法会不会?

Sch*_*999 27 java optimization jvm javac

我想知道JVM/javac是否足够聪明

// This line...
string a = foo();

string foo()
{
  return bar();
}

string bar()
{
  return some-complicated-string computation;
}
Run Code Online (Sandbox Code Playgroud)

string a = bar();
Run Code Online (Sandbox Code Playgroud)

或者在发布案例中删除对foo()的不必要调用(因为无法访问的代码):

string a = foo(bar());

// bar is the same
...

string foo(string b)
{
  if (debug) do-something-with(b);
}
Run Code Online (Sandbox Code Playgroud)

对于第一个例子我的感觉是肯定的,对于第二个例子我的感觉是"不太确定",但是有人可以给我一些指示/链接来确认吗?

Viv*_*ath 29

javac将呈现字节码,它是生成字节码的原始Java程序的忠实表示(除非在某些情况下可以优化:常量折叠死代码消除).但是,JVM在使用JIT编译器时可以执行优化.

对于第一个场景,看起来JVM支持内联(请参阅此处的方法 ,在此处查看JVM上的内联示例).

我找不到任何方法内联自己执行的例子javac.我尝试编译一些示例程序(类似于你在问题中描述的程序),并且它们似乎都没有直接内联方法,即使它是final.似乎这些优化是由JVM的JIT编译器完成的,而不是由javac.这里的方法中 提到的"编译器" 似乎是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;
   }
}
Run Code Online (Sandbox Code Playgroud)

编译为以下字节码:

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

}
Run Code Online (Sandbox Code Playgroud)

请注意,字节码有一个sipush 300而不是aload' getfields和一个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

}
Run Code Online (Sandbox Code Playgroud)

这里也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";
   }
}
Run Code Online (Sandbox Code Playgroud)

它给出了以下字节码:

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

}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,foo完全没有调用,baz因为if块内的代码实际上是"死".

Sun(现在的Oracle)HotSpot JVM结合了字节码的解释以及JIT编译.当字节码呈现给JVM时,代码最初被解释,但JVM将监视字节码并挑选出经常执行的部分.它将这些部分转换为本机代码,以便它们运行得更快.对于那些频繁使用的字节码,此编译未完成.这也是因为编译有一些开销.所以这真的是一个权衡问题.如果您决定将所有字节码编译为本机代码,则代码可能会有很长的启动延迟.

除了监视字节码之外,JVM还可以在解释和加载字节码时执行字节码的静态分析,以执行进一步的优化.

如果您想了解JVM执行的特定优化类型,Oracle的此页面非常有用.它描述了HotSpot JVM中使用的性能技术.

  • @ratchetfreak显然没有作用。“ final”与该方法是否将内联无关,将方法标记为“ final”并不一定意味着将其内联。请查看Brian Goetz的[this](http://www.ibm.com/developerworks/java/library/j-jtp1029/index.html)文章。 (2认同)
  • Javac一定不能内联函数,因为这会破坏Java中的堆栈跟踪,例如,我们将不再知道执行某种方法的方式(不仅对调试不利,而且Java中的安全性概念还取决于我们知道在哪个函数中执行,以及走栈)。JIT存储其他信息,以便在进行内联时可以纠正此问题。关于JVM:内联基本上是最重要的优化,因为它可以实现大量其他优化,否则它们将非常复杂。因此,JIT通常会积极地内联。 (2认同)