为什么Java编译器11使用invokevirtual调用私有方法?
当使用OpenJDK 8的Java编译器编译下面的代码时,对为什么Java编译器11使用invokevirtual调用私有方法?,java,java-8,jvm,javac,java-11,Java,Java 8,Jvm,Javac,Java 11,当使用OpenJDK 8的Java编译器编译下面的代码时,对foo()的调用是通过invokespecial完成的,但是当使用OpenJDK 11时,会发出invokevirtual 公共类调用{ 公开作废通知(){ foo(); } 私有void foo(){} } 使用javac1.8.0282时javap-v-p的输出: public void call(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code:
foo()
的调用是通过invokespecial
完成的,但是当使用OpenJDK 11时,会发出invokevirtual
公共类调用{
公开作废通知(){
foo();
}
私有void foo(){}
}
使用javac
1.8.0282时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
的目的是允许嵌套类从外部类调用私有方法。所以我尝试了下面的代码:
公共类测试{
公共静态void main(字符串[]args){
//构建一个派生实例,以便派生.getValue()
//有些“存在”。
System.out.println(新派生的().foo());
}
公共静态类基{
公共int foo(){
返回getValue()+新嵌套().GetValueInTested();
}
私有int getValue(){
返回24;
}
私有类嵌套{
public int getValueInNested(){
//这是来自Base的getValue(),但是
//invokevirtual调用派生的版本?
返回getValue();
}
}
}
公共静态类派生的扩展基{
//让我们重新定义getValue(),看看它是否由
//从GetValueInTested()调用虚拟。
私有int getValue(){
返回100;
}
}
}
使用11编译此代码时,我们可以在javap
的输出中看到invokevirtual
在foo()
和getValueInNested()中都使用:
所有这些都有点令人困惑,并提出了一些问题:
- 为什么使用
invokevirtual
调用私有方法?是否有一个用例,用invokespecial
替换它是不等价的
- 在
Nested.getValueInNested()
中对getValue()
的调用如何不从Derived
中选取方法,因为它是通过invokevirtual
调用的
这是作为:基于嵌套的访问控制的一部分完成的,因此JVM可以允许从嵌套类访问私有方法
在此更改之前,编译器必须在嵌套类调用的Base
类中生成一个受包保护的合成方法。该合成方法将依次调用Base
类中的私有方法。Java11中的特性增强了JVM,使得编译器不必生成合成方法
关于invokevirtual
是否将调用派生的
类中的方法,答案是否。私有方法仍然不受运行时类的约束(这一点从未改变):
在执行invokeinterface
或invokevirtual
指令的过程中,根据(i)堆栈上对象的运行时类型和(ii)先前由指令解析的方法选择方法。选择与类或接口C和方法mR相关的方法的规则如下:
如果mR标记为ACC_PRIVATE
,则它是所选方法
编辑:
根据注释“如果从方法所有者类调用私有方法,仍然使用invokespecial是否有效,如果从嵌套类调用,则使用invokevirtual是否有效?”
正如霍尔格所提到的,是的,它是有效的,但基于,我猜为了简单起见,决定切换到invokevirtual
(我无法确认这一点,这只是一个猜测):
通过对访问规则的更改以及对字节码规则的适当调整,我们可以简化生成调用字节码的规则:
- 特别是对于私有的nestmate构造函数
- 对于私有非接口、nestmate实例方法
- 调用私有接口的接口、nestmate实例方法;及
- 私有nestmate的invokestatic,静态方法
另一个有趣的注释来自:
传统上,invokevirtual
用于调用private
成员,尽管invokevirtual
也具有此功能。我们需要调用不同类中的private
方法来使用invokevirtual
,而不是干扰invokespecial
强制执行的关于超类型的复杂规则
补充一个已经很好的答案
我认为在已经提供和接受的答案中添加更多信息是合适的,尽管这不是严格必要的,但可能有助于扩大理解,因此这符合SO用户的最佳利益
基于嵌套的访问控制
在Java11之前的早期版本中,正如在接受的答案中所指出的,编译器需要
创建桥接方法,以允许类在这种情况下访问彼此的私有成员
条件这些方法在执行上下文中被调用,编译器在执行上下文中将代码插入到正在运行的程序中
这样做既增加了部署的应用程序的大小,也增加了复杂性,而且更难理解幕后发生的事情。
Java11引入了基于嵌套的访问控制的概念。
除了嵌套的概念和JVM中相关的访问规则,
这允许类和接口相互嵌套
嵌套类型可以是私有字段、方法和构造函数
使用更新的反射API,您现在可以查询有关
基于嵌套的访问控制
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