![](/img/trans.png)
[英]Java bytecode operation 'invokevirtual' does not keep consistency for the methods inherited by Object
[英]Why does the Java compiler 11 use invokevirtual to call private methods?
当使用 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
的 Output :
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
的 Output :
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()
中都使用了 invokevirtual:
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
调用的?这是作为https://openjdk.java.net/jeps/181的一部分完成的:基于嵌套的访问控制,因此 JVM 可以允许从嵌套类访问私有方法。
在此更改之前,编译器必须在嵌套 class 调用的Base
class 中生成一个受包保护的合成方法。 该合成方法将依次调用Base
class 中的私有方法。 Java 11 中的功能增强了 JVM 以允许编译器不必生成合成方法。
关于invokevirtual
是否会调用Derived
class 中的方法这一点,答案是否定的。 私有方法仍然不受运行时 class 的方法选择的影响(这从未改变过):
在执行
invokeinterface
或invokevirtual
指令期间,根据(i)堆栈上object的运行时类型和(ii)先前由指令解析的方法来选择方法。 select 一个方法关于 class 或接口 C 和一个方法 m ZE1E1D3D405731283DEECEE6的规则如下:
- 如果 m R标记为
ACC_PRIVATE
,则它是选定的方法。
编辑:
基于评论“如果私有方法是从方法所有者 class 调用的,并且如果从嵌套的 class 调用它,则使用 invokevirtual 是否仍然有效?”
正如 Holger 提到的,是的,它是有效的,但是基于JEP ,我猜为了简单起见,决定切换到invokevirtual
(虽然我无法证实这一点,这只是一个猜测):
随着访问规则的改变,以及对字节码规则的适当调整,我们可以允许生成调用字节码的简化规则:
- invokespecial 用于私有的 nestmate 构造函数,
- invokevirtual 用于私有非接口,nestmate 实例方法,
- 私有接口的invokeinterface,nestmate实例方法; 和
- 私人巢友的invokestatic,static方法
来自JDK-8197445 的另一个有趣的注释:JEP 181 的实现:基于嵌套的访问控制:
传统上,
invokespecial
用于调用private
成员,尽管invokevirtual
也具有此功能。 我们不需要在不同的 class 中调用private
方法来使用invokevirtual
,而不是扰乱由invokespecial
强制执行的有关超类型的复杂规则。
认为在已经提供和接受的答案中添加更多信息是合适的,尽管这不是绝对必要的,但它可能有助于扩大理解,因此它符合 SO 用户的最大利益。
在早期版本中,在 Java 11 之前,正如@ma在接受的答案中已经指出的那样,编译器需要创建桥接方法以允许类在这种情况下访问彼此的私有成员。 这些可访问性扩大的桥接方法在执行上下文中被调用,编译器将代码插入到正在运行的程序中。
这样做会增加已部署应用程序的大小并增加复杂性,而且更难理解幕后发生的事情。
Java 11引入了基于嵌套的访问控制的概念。 除了嵌套对象的概念和JVM中的相关访问规则外,这还允许类和接口相互嵌套。
嵌套类型可以是私有字段、方法和构造函数。
使用更新的反射 API 您现在可以查询有关基于嵌套的访问控制功能的信息。
Java 11 中的一些新优点
getNestHost()
方法用于获取巢主机的名称, isNestmateOf()
方法可用于检查class是否为巢友。 此外, getNestMembers()
方法返回一个嵌套成员数组。
这是一个通用示例的链接,由 Baeldung.com 提供,基于巢的访问控制使得好处非常突出恕我直言。
请注意,反汇编代码中没有编译器生成的桥接方法。 此外,在上面链接的示例中,内部 class 现在可以直接调用 outerPrivate() 方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.