【问题标题】:Why InvokeVirtual used instead of InvokeSpecial when calling class.NewInstance()?为什么调用 class.NewInstance() 时使用 InvokeVirtual 而不是 InvokeSpecial?
【发布时间】:2017-03-05 15:25:15
【问题描述】:

我正在研究以下 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) {

    }
}

以下字节码 sn-p 引起了我的注意,似乎不是最佳的

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

问题:

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

【问题讨论】:

    标签: java jvm java-bytecode-asm jvm-hotspot


    【解决方案1】:

    InvokeSpecial 用于指定调用是

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

    规格:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial

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

    性能不是更好吗?

    JIT 足够聪明,可以理解在这种情况下不需要遍历虚拟方法表,因此它不应该减慢执行速度。实际性能应通过实际测试进行比较,但我认为没有理由预期会有显着差异。

    【讨论】:

    • 特别是 Classfinal 所以唯一的虚方法是对父类方法的调用。
    • 我进行了编辑。 InvokedDynamic 是一个错字。我的意思是说 InvokeVirtual。
    • 我确实查看了 InvokeSpecial 的文档。我还检查了 InvokeVirtual docs.oracle.com/javase/specs/jvms/se8/html/… Invoke 实例方法;基于类的调度仍然无法理解为什么在这里使用invokevirtual,因为类是最终的。可以回答可能是 InvokedSpecial 操作码只能用于相同/超类的方法。
    • InvokeVirtual 用于所有虚拟方法。 javac 没有做它可以做的所有分析。我想它只是在方法调用指令编译时不使用关于类final 修饰符的信息。是的,InvokeSpecial 仅适用于一组有限的方法。
    【解决方案2】:

    目标方法或类是final这一事实永远不会改变使用其他类/调用方法的类的编译形式。

    这是 JLS 第 13 章规定的。“二进制兼容性”,§13.4.17, final methods

    将声明为 final 的方法更改为不再声明为 final 不会破坏与预先存在的二进制文件的兼容性。

    同样§13.4.2. final Classes

    将声明为 final 的类更改为不再声明为 final 不会破坏与预先存在的二进制文件的兼容性。

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

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

    【讨论】:

    • final 真的能保证什么吗?修改器可以在运行时删除,并且可以在编译期间替换假源,类加载是惰性的,所以我认为应该允许它。你有没有其他说明的链接?
    • @Sergey Fedorov:嗯,正如我回答的第一部分所解释的那样,在加载类之前,final 完全没有影响,正如 二进制兼容性 i> 规范。我的答案的最后一句话仅在加载类并且JVM基于该方法永远不会被覆盖的假设开始优化之后才与JVM相关。然后,如果方法是final,JVM 将拒绝覆盖尝试。但是,这仍然是特定于 JVM 实现的。例如。如果 JVM 允许通过 Instrumentation 删除 final,则它必须支持在这种情况下进行反优化。
    • afaik,热点 jit 确实支持去优化,但这不是重点。修饰符可以仅通过反射来删除,这不是特定于 jvm 的,并且至少适用于字段。到目前为止,我认为它也应该与方法一起使用,所以实际上final 在编译完成后没有任何意义。你知道任何可以反驳这一点的证据吗?
    • @Sergey Fedorov:关于你可以用“只是反射”做什么的谣言似乎很多。反射是一种只读工具,它缓存一些数据。您可以通过访问覆盖来操作这些私有缓存的数据(这怎么可能不是特定于实现的?),但这只会影响 Reflection 随后报告的内容(直到清除缓存的信息)。您真的相信,JVM 在加载类时会向反射询问修饰符吗?给我看“任何可以证明这一点的证据”……
    猜你喜欢
    • 2012-11-25
    • 2010-09-16
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 2014-08-09
    • 1970-01-01
    • 1970-01-01
    • 2012-02-26
    相关资源
    最近更新 更多