14

Let's imagine we have the following classes:

public class Message extends Object {}

public class Logger implements ILogger {
 public void log(Message m) {/*empty*/}
}

and the following program:

public static void main(String args[]) {
  ILogger l = new Logger();
  l.log((Message)null); // a)
  l.log(new Message()); // b)
}

Will the Java compiler strip out statements a and b ? In both cases (stripping or not stripping), what is the rationale behind the Java compiler's decision ?

gnat
  • 6,213
  • 108
  • 53
  • 73
David Andreoletti
  • 4,485
  • 4
  • 29
  • 51
  • 1
    Using `I` in front of interfaces http://stackoverflow.com/a/2814831/24396 – Steve Kuo Jan 08 '13 at 02:57
  • It will probably inline the empty methods which results in little or no code. – Steve Kuo Jan 08 '13 at 02:58
  • 1
    If your `main` method is not in `Logger` class compiler shouldn't optimize it since `Logger` class can be recompiled later and this time with code in `log` method. – Pshemo Jan 08 '13 at 03:10
  • Your second call to log() probably won't ever get optimized away because the Message() constructor could potentially have side effects. – ccleve Oct 24 '18 at 17:21

6 Answers6

17

Will the Java compiler strip out statements a and b ?

The javac (source to bytecode) compiler won't strip either call. (It is easy to check this by examining the bytecodes; e.g. looking at the javap -c output.)

In both cases (stripping or not stripping), what is the rationale behind the Java compiler's decision ?

Conformance with the JLS :-).

From a pragmatic perspective:

  • If the javac compiler optimized the calls away, a Java debugger wouldn't be able to see them at all ... which would be rather confusing for the developer.
  • Early optimization (by javac) would result in breakage if the Message class and the main class were compiled / modified independently. For example, consider this sequence:

    • Message is compiled,
    • the main class is compiled,
    • Message is edited so that log does something ... and recompiled.

    Now we have an incorrectly compiled main class that doesn't do the right thing at a and b because the prematurely inlined code is out of date.


However, the JIT compiler might optimize the code at runtime in a variety of ways. For instance:

  • The method calls in a and b may be inlined if the JIT compiler can deduce that no virtual method dispatching is required. (If Logger is the only class used by the application that implements ILogger this a no-brainer for a good JIT compiler.)

  • After inlining the first method call, the JIT compiler may determine that the body is a noop and optimize the call away.

  • In the case of the second method call, the JIT compiler could further deduce (by escape analysis) that the Message object doesn't need to be allocated on the heap ... or indeed at all.

(If you want to know what the JIT compiler (on your platform) actually does, Hotspot JVMs have a JVM option that dumps out the JIT-compiled native code for selected methods.)

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • So If I put a breakpoint in either `a` or `b`, is it guaranteed that the JIT compiler will _not_ optimize the respective statements away? – mucaho May 30 '16 at 13:01
  • 1
    That is correct. Read about "dynamic deoptimization" here: http://www.oracle.com/technetwork/java/whitepaper-135217.html – Stephen C May 30 '16 at 13:12
  • @StephenC Thanks, that makes sense, specifically this part _"Full-speed debugging: the Java HotSpot VM utilizes dynamic deoptimization technology to support debugging of applications at full speed. In earlier Java virtual machine implementations, when debugging support was enabled, the application was run only in the interpreter. ..."_ – mucaho May 31 '16 at 17:43
6

Disassembling the following file (with javap -c) suggests they are not stripped out by the 1.7.0 compiler when compiling down to bytecode:

public class Program
{
    public static class Message extends Object {}

    public interface ILogger {
        void log(Message m);
    }

    public static class Logger implements ILogger {
        public void log(Message m) { /* empty */ }
    }

    public static void main(String[] args) {
        ILogger l = new Logger();
        l.log((Message)null); // a)
        l.log(new Message()); // b)
    }
}

The result is below. The key bits are the invokes on lines 13 and 26.

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

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Program$Logger
       3: dup
       4: invokespecial #3                  // Method Program$Logger."<init>":()V
       7: astore_1
       8: aload_1
       9: aconst_null
      10: checkcast     #4                  // class Program$Message
      13: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      18: aload_1
      19: new           #4                  // class Program$Message
      22: dup
      23: invokespecial #6                  // Method Program$Message."<init>":()V
      26: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      31: return
}

EDIT: However, as @mikera pointed out, it's likely that the JIT compiler will do further optimizations when the program runs, which may be able to eliminate the calls. I don't know enough about the details to comment on that unfortunately.

SIDE NOTE: You might be interested in this link, which deals with performance techniques used by the Hotspot JVM:

https://wikis.oracle.com/display/HotSpotInternals/PerformanceTechniques

Stuart Golodetz
  • 20,238
  • 4
  • 51
  • 80
  • 3
    +1 for doing the disassembly, but note that the JIT compiler is likely to be doing the actual elimination after this stage..... so it doesn't actually prove whether or not these calls get optimised away in the end. – mikera Jan 08 '13 at 02:51
  • 1
    In fact, the `javac` compiler does *minimal* optimization. – Stephen C Jan 08 '13 at 07:48
3

Probably eventually, definitely not immediately, and not necessarily ever. The JIT makes no guarantees, especially not for a method that only gets called a couple times. (It would probably get categorized as simply inlining the log call, and the inlined code happening to be...nothing.)

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
3

It is impossible to say definitively - it will depend on the implementation of the JVM / Java compiler.

A smart enough compiler can prove that neither statements have an effect, and can therefore eliminate them. I believe most modern JVMs will do this, though you would need to test on your specific configuration to be sure.

a) is easier to optimise away than b) since b) includes a constructor call, which the compiler also needs to prove has no side effects before it can optimise away the whole statement.

Note that you'd expect this kind of elimination to be done by the JIT compiler rather than the Java compiler itself, i.e. bytecode would probably be generated that included the log function calls, but this is later optimised away by the JIT compiler when it compiles down to native code.

Also, since the JIT can recompile on runtime statistics etc., it is possible that the code will be there to start with, but get compiled away later on successive optimisations.

mikera
  • 105,238
  • 25
  • 256
  • 415
1

I don't think java compiler will remove the call because the called method is empty, because you can change the method in future also without making any change in the main method.

Pankaj
  • 5,132
  • 3
  • 28
  • 37
1

The Server JIT will certainly inline and eventually eliminate the code altogether if you make the reference final: final ILogger l = new Logger(); In a modern JVM most of the optimization is performed by the JIT.