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

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的输出:

  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 的目的似乎是允许嵌套的 classes 从外部 class 调用私有方法。所以我尝试了下面的代码:

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 的输出中看到 invokevirtualfoo()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[=16=]:LTest$Base;

         // ** HERE **
         4: invokevirtual #3  // Method Test$Base.getValue:()I
         7: ireturn

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

这是作为 https://openjdk.java.net/jeps/181 的一部分完成的:基于嵌套的访问控制,以便 JVM 可以允许从嵌套的 classes.

访问私有方法

在此更改之前,编译器必须在 Base class 中生成受包保护的合成方法,嵌套的 class 会调用该方法。该合成方法将依次调用 Base class 中的私有方法。 Java 11 中的功能增强了 JVM,无需编译器生成合成方法。

关于invokevirtual是否会调用Derivedclass中的方法这一点,答案是否定的。私有方法仍然不受运行时 class 的 method selection 约束(这一点从未改变):

During execution of an invokeinterface or invokevirtual instruction, a method is selected with respect to (i) the run-time type of the object on the stack, and (ii) a method that was previously resolved by the instruction. The rules to select a method with respect to a class or interface C and a method mR are as follows:

  1. If mR is marked ACC_PRIVATE, then it is the selected method.

编辑:

基于评论“如果从方法所有者调用私有方法,仍然使用 invokespecial 是否有效 class 如果从嵌套 class?"

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

With the change to the access rules, and with suitable adjustments to byte code rules, we can allow simplified rules for generating invocation bytecodes:

  • invokespecial for private nestmate constructors,
  • invokevirtual for private non-interface, nestmate instance methods,
  • invokeinterface for private interface, nestmate instance methods; and
  • invokestatic for private nestmate, static methods

来自 JDK-8197445 : Implementation of JEP 181: Nest-Based Access Control 的另一个有趣的注释:

Traditionally, invokespecial is used to invoke private members, though invokevirtual also has this capability. Rather than perturb the complex rules about supertypes enforced by invokespecial, we require invocations of private methods in a different class to use invokevirtual.

补充一个已经很好的答案

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

基于巢的访问控制

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

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

Java 11引入了基于巢的访问控制的概念。 连同 nestmates 的概念和 JVM 中的相关访问规则, 这允许 classes 和接口相互嵌套。

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

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

Java11

中的一些新优点

getNestHost()方法用于获取nest主机名,isNestmateOf()方法 可用于检查 class 是否是 nestmate。 此外,getNestMembers() 方法 returns 嵌套成员数组。

这是一个 link 的通用示例,由 Baeldung.com、Nest Based Access Control 提供,恕我直言,它的好处非常突出。

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