Does javac also inline?

2019-07-13 04:47发布

问题:

I was playing around with javap and some very simple code and that raised a - hopefully simple - question.

here is the code first:

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);
    }

}

Now I compiled the code and looked at the output (omitting -verbose for now).

$ 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        
}

Now all this makes sense and I understand the different byte codes, but the questions that came to my mind are:

  • I see "m1" and "m2" mentioned in the invokestatic calls, so they are somehow called, but I dont see their actual bytecode outputs in the javap call!
  • Now, are they inlined or just do not show up? And if so, why?

Again this question is just of pure interest of how javac handles this stuff internally. Thanks!

回答1:

They are there, but the default flags you are using doesn't show them as they are private methods. In order to see the definition for both m1 & m2 as well, use

javap -p -c .\Main.class

This will show all the internal members including private and public. This is what you will get if you use above command.

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       
}


回答2:

Javac does no method inlining whatsoever. It leaves the JVM to be responsible for this and other optimization at runtime. The JVM (at least the Oracle one) is very good at inlining, and will inline to multiple levels. It can even inline some polymorphic method calls if they are found to be monomorphic at runtime (i.e., at a particular call site, it tries to detect when there is only one possible method implementation that could be called even though the method is overrideable).

You can also use a postprocessor like ProGuard to inline and optimize Java code after compilation.

P.S. creating new String objects like this:

return new String("foobar");

is wasteful and always unnecessary. You can simply do:

return "foobar";