Java 消除空检查会导致错误代码?

Java Null check elimination can cause bad code?

最近,我阅读了 here 关于 JVM 优化的内容,非常棒,
但是我有一个优化问题,即空检查消除(或不常见的陷阱)

总结一下Null Check Elimination去掉了一个if (obj == null) ...希望最好,而如果遇到Segmentation Fault它重新编译代码,这次包括被忽略的 if (obj == null) ....

我的问题是,鉴于:

bool foo(MyClass obj)
{
   if(obj == null)
      return false;
   m_someVar++;
   obj.doSomething(m_someVar);
}

因为取消了 Null-Check,只有在 m_someVar++ 之后才需要。
foo 会在 c 为空时额外执行 m_someVar++ 一次吗?

编辑:

是否有一些资源可以更深入地解释此优化的实现,解释此优化如何使代码在语义上保持相同?

谢谢

有很多方法可以避免这个问题:评论中已经提到延迟或撤消增量。 JVM 有一大堆技巧,其中一些适用:

  • obj.doSomething(m_someVar) 是非虚拟方法调用(privatefinal 方法,或从未被覆盖的方法)时,它可能需要显式空检查(* ) 因为没有 SEGV,但必须在调用之前抛出 NPE。我假设该方法未内联,否则应在内联后应用此分析。
  • obj.doSomething(m_someVar) 是虚方法调用时,则必须执行 "method dispatch",即类似 obj.getClass().getPointerToMethod("doSomething(int)") 的操作以确定要调用的具体方法。我写 "something like" 是因为从字面上看这样做非常耗时,并且会尽可能地进行优化。如果 obj 恰好是 null.
  • ,则可以将此分派移动到增量之上,它本身将抛出 NPE
  • 如果你幸运的话,调度可能看起来像 if (obj.getClass() != MyClass.class) uncommon_trap();,这是最简单的情况(称为 "monomorphic call site"),但即使这样也涉及取消引用 obj 并且你再次获得 null.
  • 的 NPE

(*) 空检查可能看起来像 obj.getClass(),在汇编程序中它是从相对于 obj 指针的固定偏移量加载的单个指令。当 obj == null 然后一个未映射的页面被命中。

它认为你误解了空检查消除的实际含义。

实际上,它是关于消除当您(例如)在实例上调用方法时隐含的空检查。

在您的示例中,显式 if(obj == null) 不会被删除。这将(正如您观察到的那样)改变程序的行为。消除空检查将是不正确的优化。

但是,在 HERE 处的隐式空检查如下:

bool foo(MyClass obj)
{
   if(obj == null)
      return false;
   m_someVar++;
   obj.doSomething(m_someVar);  // HERE
}

可以被消除,因为 JIT 编译器应该能够推断出 obj 在代码到达该点时将始终为非空。 (虽然这是一种不同类型的空检查优化。)


Is there some source for deeper explanation on the implementation of this optimizations ...

我不这么认为。至少,没有什么是确定的。 (除非你计算 JIT 编译器源代码中可能的注释!)

未指定 JIT 优化器行为。它基本上可以做任何事情,只要它不违反 JLS 中定义的 Java 的语义。

... which explain how does this optimization keeps the code semantically same?

正如我所解释的,您正在阅读的乐观空检查消除优化不适用于您的示例。 (它 使代码按照 JLS 不正确地运行。)