Why does the Java compiler 11 use invokevirtual to call private methods?
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
invokevirtualis used to call private methods? Is there a use case where replacing it by aninvokespecialwould not be equivalent? - How does the call to
getValue()inNested.getValueInNested()not pick the method fromDerivedsince it is called viainvokevirtual?
invokevirtualandinvokeinterfacecan call private methods of classes and interfaces respectively. This is to avoid overcomplicating already non-trivial access rules ofinvokespecial.invokevirtualis not better or worse thaninvokespecialperformance-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 toinvokespecial- there is even ajavacflag for this:-XDdisableVirtualizedPrivateInvoke– apangin Apr 23 at 11:30invokeinterfaceinstruction (its existence as a whole) and perhaps even the distinction betweenMethodrefandInterfaceMethodrefin 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