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

JavaJava 8JvmJavacJava 11

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

This was done as part of https://openjdk.java.net/jeps/181: Nest-Based Access Control, so that the JVM can allow access to private methods from nested classes.

Before that change, the compiler would have to generate a package-protected synthetic method in the Base class, which the nested class invokes. That synthetic method would in turn call the private method in the Base class. The feature in Java 11 enhances the JVM to allow that without the compiler having to generate a synthetic method.

Concerning the point on whether invokevirtual will call the method in the Derived class, the answer is no. The private method is still not subject to method selection of the runtime class (this has never changed):

>During execution of an invokeinterface or invokevirtual instruction, a method is selected with respect to (i) the run-time type of the object on the stack, and (ii) a method that was previously resolved by the instruction. The rules to select a method with respect to a class or interface C and a method mR are as follows:

>>1. If mR is marked ACC_PRIVATE, then it is the selected method.

EDIT:

Based on the comment "Would it be valid to still use invokespecial if the private method is called from the method owner class and use invokevirtual if it is called from a nested class?"

As Holger mentioned, yes it is valid, but based on the JEP, I'm guessing a decision was made to switch to invokevirtual for simplicity (I cannot confirm this though, it's just a guess):

>With the change to the access rules, and with suitable adjustments to byte code rules, we can allow simplified rules for generating invocation bytecodes:

>>- invokespecial for private nestmate constructors, >>- invokevirtual for private non-interface, nestmate instance methods, >>- invokeinterface for private interface, nestmate instance methods; and >>- invokestatic for private nestmate, static methods

Another interesting note from JDK-8197445 : Implementation of JEP 181: Nest-Based Access Control:

>Traditionally, invokespecial is used to invoke private members, though invokevirtual also has this capability. Rather than perturb the complex rules about supertypes enforced by invokespecial, we require invocations of private methods in a different class to use invokevirtual.

Solution 2 - Java

Top-Up To an already good answer

Thought it would be proper to add some more info to the answer already provided and accepted, although it isn't strictly necessary, it may help broaden the understanding, hence it's within SO users best interest.

Nest-Based Access Control

In earlier versions, before Java 11, as already pointed out by @m-a in the accepted answer, the compiler would need to create bridge methods to allow classes to access each other's private members in such conditions. These accessibility-broadening bridge methods gets called in the execution context, where the compiler inserts code into a running program.

Doing this increases both the size of the deployed applications and increases the complexity, plus it makes it harder to understand what is going on behind the scenes.

Java 11 introduced the concept of nest-based access control. Along with the notion of nestmates and the associated access rules within the JVM, this allows classes and interfaces to be nested within each other.

Nested types can be private fields, methods, and constructors.

Using the updated reflection API you can now query for information about the nest-based access control functionality.

Some New Goodness in Java 11

The getNestHost() method is used to get the name of nest host and the isNestmateOf() method can be used to check whether a class is a nestmate. Also, getNestMembers() method returns an array of nest members.

Here is a link to a generic example, courtesy of Baeldung.com, Nest Based Access Control that makes the benefits stand out pretty well IMHO.

Notice that there isn't a compiler generated bridging method in the disassembled code. Also, the Inner class can now make a direct call to the outerPrivate() method in the example linked to above.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJulienView Question on Stackoverflow
Solution 1 - JavaM AView Answer on Stackoverflow
Solution 2 - JavaC. SederqvistView Answer on Stackoverflow