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
invokevirtual
is used to call private methods? Is there a use case where replacing it by aninvokespecial
would not be equivalent? - How does the call to
getValue()
inNested.getValueInNested()
not pick the method fromDerived
since it is called viainvokevirtual
?
invokevirtual
andinvokeinterface
can call private methods of classes and interfaces respectively. This is to avoid overcomplicating already non-trivial access rules ofinvokespecial
.invokevirtual
is not better or worse thaninvokespecial
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 toinvokespecial
- there is even ajavac
flag for this:-XDdisableVirtualizedPrivateInvoke
– apangin Apr 23 at 11:30invokeinterface
instruction (its existence as a whole) and perhaps even the distinction betweenMethodref
andInterfaceMethodref
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