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

2022-08-31 20:19:01

当使用 OpenJDK 8 中的 Java 编译器编译以下代码时,对 的调用是通过 一个 来完成的,但是当使用 OpenJDK 11 时,将发出 一个。foo()invokespecialinvokevirtual

public class Invoke {
  public void call() {
    foo();
  }

  private void foo() {}
}

使用 1.8.0_282 时的输出:javap -v -pjavac

  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

使用 11.0.10 时的输出:javap -v -pjavac

  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

我不明白为什么在这里使用,因为不能覆盖。invokevirtualfoo()

在挖掘了一下之后,似乎 on 私有方法的目的是允许嵌套类从外部类调用私有方法。所以我尝试了下面的代码: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编译此代码,我们可以在输出中看到它在和中都使用:javapinvokevirtualfoo()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

所有这些都有点令人困惑,并提出了一些问题:

  • 为什么用于调用私有方法?有没有一个用例,用 代替它不等于?invokevirtualinvokespecial
  • 调用 in 如何不从中选取方法,因为它是通过 调用的?getValue()Nested.getValueInNested()Derivedinvokevirtual

答案 1

这是作为 https://openjdk.java.net/jeps/181:基于嵌套的访问控制的一部分完成的,因此 JVM 可以允许从嵌套类访问私有方法。

在进行此更改之前,编译器必须在类中生成受包保护的综合方法,嵌套类将调用该方法。该合成方法将反过来调用类中的私有方法。Java 11 中的特性增强了 JVM,使其无需编译器生成综合方法即可实现。BaseBase

关于是否会在类中调用方法的问题,答案是否定的。私有方法仍然不受运行时类的方法选择的约束(这从未改变):invokevirtualDerived

在执行 or 指令期间,根据 (i) 堆栈上对象的运行时类型和 (ii) 先前由指令解析的方法选择方法。选择与类或接口 C 相关的方法和方法 mR 的规则如下:invokeinterfaceinvokevirtual

  1. 如果 mR 被标记为 ,则它是选定的方法。ACC_PRIVATE

编辑:

基于注释“如果从方法所有者类调用私有方法,则仍然使用 invokespecial 是否有效,如果从嵌套类调用,则使用 invokevirtual?

正如Holger所提到的,是的,它是有效的,但是基于JEP,我猜为了简单起见,我决定切换到(虽然我无法确认这一点,但这只是一个猜测):invokevirtual

通过对访问规则的更改以及对字节码规则的适当调整,我们可以允许用于生成调用字节码的简化规则:

  • invokespecial for private nestmate constructors,
  • 调用私有非接口的虚拟,nestmate 实例方法,
  • 调用私有接口的接口,嵌套实例方法;和
  • 调用私有 nestmate 的静态静态方法

来自JDK-8197445的另一个有趣的说明:JEP 181的实现:基于嵌套的访问控制

传统上,用于调用成员,但也具有此功能。我们不需要扰动 由 强制实施的超类型的复杂规则,而是需要调用不同类中的方法才能使用 。invokespecialprivateinvokevirtualinvokespecialprivateinvokevirtual


答案 2

充值 到一个已经很好的答案

认为在已经提供和接受的答案中添加一些更多信息是适当的,尽管这不是绝对必要的,但它可能有助于扩大理解,因此它符合SO用户的最佳利益。

基于嵌套的访问控制

在Java 11之前的早期版本中,正如@m-a在接受的答案中已经指出的那样,编译器需要创建桥接方法,以允许类在这种情况下访问彼此的私有成员。这些扩展辅助功能的桥接方法在执行上下文中调用,编译器在其中将代码插入到正在运行的程序中。

这样做会增加已部署应用程序的大小并增加复杂性,并且会使其更难理解幕后发生的事情。

Java 11 引入了基于嵌套的访问控制的概念。除了嵌套的概念和 JVM 中的关联访问规则之外,这还允许类和接口相互嵌套。

嵌套类型可以是私有字段、方法和构造函数。

使用更新的反射 API,您现在可以查询有关基于嵌套的访问控制功能的信息。

Java 11 中的一些新好处

该方法用于获取嵌套主机的名称,该方法可用于检查类是否为嵌套对象。此外,方法返回嵌套成员的数组。getNestHost()isNestmateOf()getNestMembers()

这是一个通用示例的链接,由 Baeldung.com 提供,基于Nest的访问控制,恕我直言,它的好处非常出色。

请注意,在反汇编的代码中没有编译器生成的桥接方法。此外,Inner 类现在可以在上面链接的示例中直接调用 outerPrivate() 方法。


推荐