Why does the Java compiler 11 use invokevirtual to call private methods?

45

When compiling the code below with the Java compiler from OpenJDK 8, the call to foo() is done via an invokespecial, but when OpenJDK 11 is used, an invokevirtual is emitted.

public class Invoke {
  public void call() {
    foo();
  }

  private void foo() {}
}

Output of javap -v -p when javac 1.8.0_282 is used:

  public void call();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2      // Method foo:()V
         4: return

Output of javap -v -p when javac 11.0.10 is used:

  public void call();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #2      // Method foo:()V
         4: return

I don't get why invokevirtual is used here since there cannot be an override of foo().

After digging a bit it seems that the purpose of invokevirtual on private methods is to allow nested classes to call private methods from the outer class. So I tried the code below:

public class Test{
  public static void main(String[] args) {
    // Build a Derived such that Derived.getValue()
    // somewhat "exists".
    System.out.println(new Derived().foo());
  }

  public static class Base {

    public int foo() {
      return getValue() + new Nested().getValueInNested();
    }

    private int getValue() {
      return 24;
    }

    private class Nested {

      public int getValueInNested() {
        // This is getValue() from Base, but would
        // invokevirtual call the version from Derived?
        return getValue();
      }
    }
  }

  public static class Derived extends Base {

    // Let's redefine getValue() to see if it is picked by the
    // invokevirtual from getValueInNested().
    private int getValue() {
      return 100;
    }
  }
}

Compiling this code with 11, we can see in the output of javap that invokevirtual is used both in foo() and in getValueInNested():

  public int foo();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=4, locals=1, args_size=1
         0: aload_0

         // ** HERE **
         1: invokevirtual #2  // Method getValue:()I
         4: new           #3  // class Test$Base$Nested
         7: dup
         8: aload_0
         9: invokespecial #4  // Method Test$Base$Nested."<init>":(LTest$Base;)V
        12: invokevirtual #5  // Method Test$Base$Nested.getValueInNested:()I
        15: iadd
        16: ireturn
  public int getValueInNested();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #1  // Field this$0:LTest$Base;

         // ** HERE **
         4: invokevirtual #3  // Method Test$Base.getValue:()I
         7: ireturn

All of this is a bit confusing and raises some questions:

  • Why invokevirtual is used to call private methods? Is there a use case where replacing it by an invokespecial would not be equivalent?
  • How does the call to getValue() in Nested.getValueInNested() not pick the method from Derived since it is called via invokevirtual?
Share
Improve this question
2
  • 12
    Since JEP 181, invokevirtual and invokeinterface can call private methods of classes and interfaces respectively. This is to avoid overcomplicating already non-trivial access rules of invokespecial. invokevirtual is not better or worse than invokespecial performance-wise - it's just a modern way of calling private methods, whether of the same class or of a nestmate. And yes, it's possible to replace it back to invokespecial - there is even a javac flag for this: -XDdisableVirtualizedPrivateInvoke – apangin Apr 23 at 11:30
  • 3
    @apangin I have the feeling that the invokeinterface instruction (its existence as a whole) and perhaps even the distinction between Methodref and InterfaceMethodref in the constant pool, is even more an unnecessary complication. The clean separation between interface methods and non-interface methods at the invocation instruction was lost in Java 8 already and private methods in interfaces and nest classes made it even more blurry. And the optimizations that an interpreter could do with that distinction became irrelevant even long before Java 8. – Holger Apr 23 at 14:11

Comments

Popular posts from this blog

Meaning of `{}` for return expression

Get current scroll position of ScrollView in React Native

flutter websocket connection issue