Java 为什么在调用class.NewInstance()时使用InvokeVirtual而不是InvokeSpecial?

Java 为什么在调用class.NewInstance()时使用InvokeVirtual而不是InvokeSpecial?,java,jvm,java-bytecode-asm,jvm-hotspot,Java,Jvm,Java Bytecode Asm,Jvm Hotspot,我正在研究以下java程序的反汇编 public class ASMPlayground { private String bar; public String getBar(){ return bar; } public void setBar(String bar) throws IllegalAccessException, InstantiationException { String name = String.cla

我正在研究以下java程序的反汇编

public class ASMPlayground {
    private String bar;

    public String getBar(){
        return bar;
    }

    public void setBar(String bar) throws IllegalAccessException, InstantiationException {
        String name = String.class.newInstance();
        System.out.println(name);
    }

    public static void main(String[] args) {

    }
}
下面的字节码片段引起了我的注意,似乎是次优的

LDC Ljava/lang/String;.class
INVOKEVIRTUAL java/lang/Class.newInstance ()Ljava/lang/Object;
CHECKCAST java/lang/String
问题:

当要执行的方法依赖于对象引用时,使用InvokeVirtual。假设“类”是最终的,并且newInstance()只存在
在“类”中,为什么不使用InvokeSpecial而不是InvokeVirtual?它的性能不是更高吗?

InvokeSpecial
用于指定

超类、私有和实例初始化方法调用

规格:

Class.newInstance
既不是超类方法调用,也不是私有方法调用,也不是对初始化方法的调用。它也不是一个动态方法,
InvokeDynamic
与此无关。这两条指令在这里都不能使用,因为它将与JVM对抗。在创建JVM时,允许用“更高性能”的指令替换某些指令是可能的,但正如我在JVM中看到的那样,还没有这样做

这不是更有效吗


JIT足够聪明,可以理解在这种情况下不需要遍历虚拟方法表,所以它不应该降低执行速度。实际性能应该通过实际测试进行比较,但我看不出有什么理由期望有显著差异。

目标方法或类是
最终的这一事实不会改变使用另一个类/调用该方法的类的编译形式

这是JLS第13章规定的。“二进制兼容性”:

将声明为
final
的方法更改为不再声明为
final
不会破坏与现有二进制文件的兼容性

同样地

将声明为
final
的类更改为不再声明为
final
不会破坏与现有二进制文件的兼容性

显然,依赖于目标方法的
final
性质的调用指令将违反此规范


没有预期的相关性能影响。当符号引用必须解析为实际方法时(根据JVM的运行时表示),总是会有第一次开销。此时,JVM还可以记录这样一个事实,即该方法或其声明类实际上是调用指令的
final
,如果有好处的话。现代JVM甚至走得更远,例如,利用了一个事实,即非
final方法实际上没有被覆盖到相同的效果,尽管如果子类被加载并实例化(具有覆盖方法),则需要对这些调用进行去优化。所以唯一的区别是,
final
修饰符保证了这样的去优化是不必要的。

尤其是
Class
final
,所以唯一的虚拟方法是调用父类方法。我做了一个编辑。InvokedDynamic是一个打字错误。我的意思是InvokeVirtual。我确实查阅了InvokeSpecial的文档。我还检查了InvokeVirtualInvoke实例方法;基于类的分派仍然无法理解为什么这里使用invokevirtual,因为类是最终的。答案可能是InvokedSpecial操作码只能用于相同/超类的方法。
InvokeVirtual
用于所有虚拟方法
javac
并没有完成它所能做的所有分析。我想它只是在方法调用指令编译时不使用有关class
final
修饰符的信息。是的,
InvokeSpecial
只为有限的一组方法设计。
final
真的能保证什么吗?修饰符可以在运行时删除,假源代码可以在编译期间替换,类加载是惰性的,所以我认为应该允许这样做。谢尔盖·费多罗夫:正如我回答的第一部分所解释的,在类加载之前,
final
完全没有影响,正如二进制兼容性规范所要求的那样。我的答案的最后一句与JVM相关,只有在类被加载之后,JVM才开始基于方法永远不会被重写的假设进行优化。然后,如果方法是
final
,JVM将拒绝覆盖尝试。但是,这仍然是特定于JVM实现的。例如,如果JVM允许通过检测删除
final
,那么在这种情况下它必须支持去优化。好的,hotspot jit确实支持去优化,但这不是重点。修饰符可以通过反射移除,反射不是jvm特有的,至少可以用于字段。到目前为止,我认为它也应该与方法一起工作,所以编译完成后,
final
实际上没有任何意义。你知道有什么证据可以反驳这一点吗?@Sergey Fedorov:关于“公正思考”可以做什么,似乎有很多神话流传开来。反射是一种只读功能,用于缓存某些数据。您可以通过访问覆盖操作这些私人缓存的数据(这怎么可能不是特定于实现的?),但这只会影响反射随后报告的内容(直到清除缓存的信息)。您真的相信吗,JVM在加载类时会询问修饰符的反射?给我看“任何能证明这一点的证据”…