2

我在玩 javap 和一些非常简单的代码,这引发了一个 - 希望是简单的 - 问题。

这里首先是代码:

public class Main {


  public static void main(String[] args) throws Exception {
    System.out.println(m1());
    System.out.println(m2());
  }

    private static String  m1() {
        return new String("foobar");
    }

    private static String m2() {
        String str = "foobar";
        return new String(str);
    }

}

现在我编译了代码并查看了输出(暂时省略 -verbose)。

$ javap -c Main.class 
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: invokestatic  #3                  // Method m1:()Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: invokestatic  #5                  // Method m2:()Ljava/lang/String;
      15: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: return        
}

现在这一切都说得通了,我理解了不同的字节码,但我想到的问题是:

  • 我看到在 invokestatic 调用中提到了“m1”和“m2”,所以它们以某种方式被调用,但我在 javap 调用中没有看到它们的实际字节码输出!
  • 现在,他们是内联还是不显示?如果是这样,为什么?

同样,这个问题只是对 javac 如何在内部处理这些东西感兴趣。谢谢!

4

2 回答 2

6

它们在那里,但是您使用的默认标志不会显示它们,因为它们是私有方法。为了同时查看 m1 和 m2 的定义,请使用

javap -p -c .\Main.class

这将显示所有内部成员,包括私有和公共。如果您使用上述命令,这就是您将得到的。

PS C:\Users\jbuddha> javap -p -c .\Main.class
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: invokestatic  #3                  // Method m1:()Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: invokestatic  #5                  // Method m2:()Ljava/lang/String;
      15: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: return        

  private static java.lang.String m1();
    Code:
       0: new           #6                  // class java/lang/String
       3: dup           
       4: ldc           #7                  // String foobar
       6: invokespecial #8                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
       9: areturn       

  private static java.lang.String m2();
    Code:
       0: ldc           #7                  // String foobar
       2: astore_0      
       3: new           #6                  // class java/lang/String
       6: dup           
       7: aload_0       
       8: invokespecial #8                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      11: areturn       
}
于 2013-11-13T09:29:53.870 回答
1

Javac 没有任何方法内联。它让 JVM 负责在运行时进行此优化和其他优化。JVM(至少是 Oracle 的)非常擅长内联,并且会内联到多个级别。如果在运行时发现它们是单态的,它甚至可以内联一些多态方法调用(即,在特定的调用站点,它会尝试检测何时只有一种可能的方法实现可以被调用,即使该方法是可覆盖的)。

您还可以使用像ProGuard这样的后处理器在编译后内联和优化 Java 代码。

PS 像这样创建新的 String 对象:

return new String("foobar");

是浪费的,而且总是不必要的。你可以简单地做:

return "foobar";
于 2013-11-13T09:40:01.357 回答