java - 为什么Java编译器11使用invokevirtual调用私有(private)方法?

原文 标签 java java-8 jvm javac java-11

当使用 OpenJDK 8 中的 Java 编译器编译以下代码时,调用 foo()通过 invokespecial 完成,但是当使用 OpenJDK 11 时,invokevirtual被发射。

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

  private void foo() {}
}
javap -v -p 的输出当javac使用 1.8.0_282:
  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
javap -v -p 的输出当javac使用 11.0.10:
  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 的目的关于私有(private)方法是允许嵌套类从外部类调用私有(private)方法。所以我尝试了下面的代码:
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() :
  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是用来调用私有(private)方法的吗?是否有用 invokespecial 替换它的用例?会不会等价?
  • 如何调用getValue()Nested.getValueInNested()不要从 Derived 中选择方法因为它是通过 invokevirtual 调用的?
  • 最佳答案

    这是作为 https://openjdk.java.net/jeps/181 的一部分完成的。 :基于嵌套的访问控制,这样 JVM 可以允许从嵌套类访问私有(private)方法。
    在此更改之前,编译器必须在 Base 中生成一个受包保护的合成方法。嵌套类调用的类。该合成方法将依次调用 Base 中的私有(private)方法。类(class)。 Java 11 中的功能增强了 JVM 以允许编译器不必生成合成方法。
    关于是否 invokevirtual将调用 Derived 中的方法课,答案是否定的。私有(private)方法仍然不受 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.


    编辑:
    基于评论“如果从方法所有者类调用私有(private)方法并使用invokevirtual如果从嵌套类调用,仍然使用invokespecial是否有效?”
    正如 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.

    关于java - 为什么Java编译器11使用invokevirtual调用私有(private)方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67226178/

    相关文章:

    java - ImmutableMap 是大量键/对象/的次优选择吗?

    java - JVM堆大小不断增加(但使用的Memory()不断减少)

    java - 在 JAVA 中使用 Streams 进行聚合,这是一个好方法吗?

    Java 8 : Formatting lambda with newlines and indentation

    Java 字节码 : local variables table vs on-stack calculation

    java - 我的 Controller 在运行时工作,但 mockkmvc 测试失败,因为依赖项甚至不在类中

    java - 如何获取客户端的url

    java - Rational Rhapsody 调试 JVM

    design-patterns - 在 Clojure 的宏中声明设计模式

    java - 创建名称为 'hypermediaRepresentionModelProcessorConfigurator' 的 bean 时出错