为什么Java编译器11使用invokevirtual调用私有方法?

为什么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:

当使用OpenJDK 8的Java编译器编译下面的代码时,对
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