【发布时间】:2021-07-17 10:31:56
【问题描述】:
使用 OpenJDK 8 中的 Java 编译器编译以下代码时,对 foo() 的调用是通过 invokespecial 完成的,但是当使用 OpenJDK 11 时,会发出 invokevirtual。
public class Invoke {
public void call() {
foo();
}
private void foo() {}
}
使用javac 1.8.0_282 时javap -v -p 的输出:
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
使用javac 11.0.10 时javap -v -p 的输出:
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
我不明白为什么在这里使用invokevirtual,因为不能覆盖foo()。
经过一番挖掘,似乎invokevirtual 在私有方法上的目的是允许嵌套类从外部类调用私有方法。所以我尝试了下面的代码:
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;
}
}
}
用 11 编译这段代码,我们可以在javap 的输出中看到invokevirtual 在foo() 和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
所有这些都有点令人困惑,并提出了一些问题:
- 为什么
invokevirtual用来调用私有方法?是否存在用invokespecial替换它的用例不等效? - 在
Nested.getValueInNested()中对getValue()的调用如何不从Derived中选择方法,因为它是通过invokevirtual调用的?
【问题讨论】:
-
由于JEP 181、
invokevirtual和invokeinterface可以分别调用类和接口的私有方法。这是为了避免使invokespecial已经很重要的访问规则过于复杂。invokevirtual在性能方面并不比invokespecial好或差 - 它只是调用私有方法的一种现代方式,无论是同一个班级还是同胞。是的,可以将其替换回invokespecial- 甚至还有一个javac标志:-XDdisableVirtualizedPrivateInvoke -
@apangin 我感觉
invokeinterface指令(它作为一个整体存在),甚至可能在常量池中Methodref和InterfaceMethodref之间的区别,甚至是不必要的并发症。 Java 8 中已经丢失了调用指令中接口方法和非接口方法之间的清晰分离,接口和嵌套类中的私有方法使其更加模糊。甚至在 Java 8 之前很久,解释器可以对这种区别进行的优化就变得无关紧要了。
标签: java java-8 jvm javac java-11