JVM字节码中的NOP指令有什么作用?

22

在今天的JVM中,Java虚拟机的NOP操作码有什么实际用途吗?如果有,生成字节码时会出现哪些NOP场景?

我甚至想看一下编译成带有NOP的字节码的Java代码示例。


更新

BCEL的MethodGen类说:

在生成代码时,可能需要插入NOP操作。

我猜其他字节码生成库也是如此,正如被接受的答案所指出的那样。


通常它用于调试代码,允许在某些无法转换为字节码的地方设置断点,例如 { - vcsjones
当使用javac -g编译Java文件时,您的意思是这会出现在字节码中吗? - jbranchaud
我不相信javac会这样做。但其他编译器和调试器可能会利用那个功能。 - vcsjones
作为一个例子,使用soot生成带有一些if语句的Jimple代码会导致NOPs。 - Jus12
3个回答

12

一些NOP字节码用于由工具执行的Apache BCELASMFindBugsPMD等进行的文件转换,优化和静态分析。 Apache BCEL手册涉及了一些使用NOP进行分析和优化的用途。

JVM可能会使用NOP字节码进行JIT优化,以确保在同步安全点处的代码块被正确对齐,以避免false sharing(伪共享)。

关于使用JDK javac编译器编译的一些示例代码,其中包含NOP字节码,这是一个有趣的挑战。然而,我怀疑编译器将生成任何包含NOP字节码的class文件,因为字节码指令流仅单字节对齐。我很想看到这样的例子,但我自己想不出来。

1

我是一名有用的助手,可以为您进行文本翻译。

这是我正在处理的一些代码示例,其中nop指令被放置到字节码中(在Eclipse的Bytecode Visualizer中查看)

原始代码:

public abstract class Wrapper<T extends Wrapper<T,E>,E>
  implements Supplier<Optional<E>>, Consumer<E>
{
  /** The wrapped object. */
  protected Optional<E> inner;

  /*
   * (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  /**
   * A basic equals method that will compare the wrapped object to
   * whatever you throw at it, whether it is wrapped or not.
   */
  @Override
  public boolean equals(final Object that)
  {
    return this==that
        ||LambdaUtils.castAndMap(that,Wrapper.class,afterCast
            -> inner.equals(afterCast.inner))
        .orElseGet(()
            -> LambdaUtils.castAndMap(that,Optional.class,afterCast
                -> inner.equals(afterCast))
            .orElseGet(()
                -> Optional.ofNullable(that).map(thatobj
                    -> that.equals(inner.get()))
                .orElseGet(()
                    -> false)));
  }
}

equals(Object)方法的翻译后的字节码
public boolean equals(java.lang.Object arg0) {
    /* L27 */
    0 aload_0;                /* this */
    1 aload_1;                /* that */
    2 if_acmpeq 36;
    /* L28 */
    5 aload_1;                /* that */
    6 ldc 1;
    8 aload_0;                /* this */
    9 invokedynamic 29;       /* java.util.function.Function apply(ext.cat.wcutils.collections.Wrapper arg0) */
    12 nop;
    13 nop;
    14 invokestatic 30;       /* java.util.Optional ext.cat.wcutils.util.LambdaUtils.castAndMap(java.lang.Object arg0, java.lang.Class arg1, java.util.function.Function arg2) */
    /* L30 */
    17 aload_0;               /* this */
    18 aload_1;               /* that */
    19 invokedynamic 39;      /* java.util.function.Supplier get(ext.cat.wcutils.collections.Wrapper arg0, java.lang.Object arg1) */
    22 nop;
    23 nop;
    24 invokevirtual 40;      /* java.lang.Object orElseGet(java.util.function.Supplier arg0) */
    27 checkcast 46;          /* java.lang.Boolean */
    30 invokevirtual 48;      /* boolean booleanValue() */
    /* L37 */
    33 ifne 5;
    /* L27 */
    36 iconst_0;
    37 ireturn;
    38 iconst_1;
    39 ireturn;
}

我不确定为什么会插入这些内容。我只是希望它们不会对性能产生不利影响。

7
你使用的字节码可视化工具存在 BUG,其中并没有 nop 指令。invokedynamic 指令由五个字节组成,其中最后两个字节根据规范应该为零。显然,该字节码可视化工具不知道这一点,并假设 invokedynamic 指令只有三个字节,并将这两个零字节误解为 nop 指令。参考 JVM Spec invokedynamic - Holger
我想这很有道理。我正在等待Bytecode Visualizer for Eclipse Neon的更新。也许我们应该告诉开发者。 - HesNotTheStig

-1

无操作指令通常用于处理器流水线优化。我不确定Java目前在多大程度上使用它们。

来自维基百科

NOP最常用于计时目的,强制内存对齐,防止危险,占用分支延迟槽,或作为占位符,在程序开发中稍后替换为活动指令(或在重构会很困难或耗时的情况下替换已删除的指令)。在某些情况下,NOP可能会产生轻微的副作用;例如,在Motorola 68000系列处理器上,NOP操作码会导致流水线同步。


7
这在物理机器的操作码中是有意义的,但对于一个虚拟机器的操作码来说,这样的东西有什么用处呢? - Steven Schlansker
1
我感谢您的回答,但这个问题非常具体地涉及到JVM。 - jbranchaud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接