空指针检查顺序

时间:2017-07-22 22:14:10

标签: java jvm java-memory-model

这些代码来自:

http://hg.openjdk.java.net/code-tools/jcstress/file/tip/tests-custom/src/main/java/org/openjdk/jcstress/tests/volatiles/ReadAfterReadTest.java

public class ReadAfterReadTest {
    private final Holder h1 = new Holder();
    private final Holder h2 = h1;
    private static class Holder {
        int a;
        int trap;
    }
    @Actor
    public void actor1() {
        h1.a = 1;
    }
    @Actor
    public void actor2(II_Result r) {
        Holder h1 = this.h1;
        Holder h2 = this.h2;
        (*****)
        // Spam null-pointer check folding: try to step on NPEs early.
        // Doing this early frees compiler from moving h1.a and h2.a loads
        // around, because it would not have to maintain exception order anymore.
        (*****)
        h1.trap = 0;
        h2.trap = 0;

        // Spam alias analysis: the code effectively reads the same field twice,
        // but compiler does not know (h1 == h2) (i.e. does not check it, as
        // this is not a profitable opt for real code), so it issues two independent
        // loads.
        r.r1 = h1.a;
        r.r2 = h2.a;
    }
}

我无法通过(*****)评论突出显示作者的意思。

2 个答案:

答案 0 :(得分:2)

Java规范要求在轮询其字段之前检查h1h2实例为null,即使没有显式NPE检查 - 此功能也被称为隐式空指针检查。让我们看一下汇编代码(由JIT生成):

0x00007f1fad2429f2: mov    %rbx,0x18(%rsp)
0x00007f1fad2429f7: mov    (%rsp),%rbx
0x00007f1fad2429fb: mov    0x10(%rsp),%rcx
0x00007f1fad242a00: mov    0x28(%rsp),%r10    ;*aload
0x00007f1fad242a05: mov    0x10(%rbx),%r11d   ;*getfield h1
0x00007f1fad242a09: mov    0xc(%r11),%edx     ;*getfield a
                                              ; implicit exception: dispatches to 0x00007f1fad242b37
0x00007f1fad242a0d: mov    %r12d,0x10(%r11)   ;*putfield trap
0x00007f1fad242a11: mov    0x14(%rbx),%r9d    ;*getfield h2
0x00007f1fad242a15: mov    %r12d,0x10(%r9)    ;*putfield trap
                                              ; implicit exception: dispatches to 0x00007f1fad242b49
0x00007f1fad242a19: mov    %r10,0x28(%rsp)
0x00007f1fad242a1e: mov    %rcx,0x10(%rsp)

其中,取消引用空指针将导致进程发出SEGV信号。 VM有SEGV处理程序可以处理它,并抛出适当的NullPointerException(NPE)。

这可以被视为一个小编译器的障碍,它可以阻止一些优化,其中异常应该恰好在它发生的地方抛出。此检查可确保进一步执行代码不会导致NPE,因此编译器可以移动h1.ah2.a,因为它不再需要维护异常顺序。

答案 1 :(得分:0)

我认为他正试图尽早生成NullPointerException。也就是说:如果此时h1h2为空,则分配h1.trap = 0会抛出NPE。

下一行解释了为什么这会有所帮助:

Doing this early frees compiler from moving h1.a and h2.a loads - 我认为它与编译器和JIT的一些优化有关。

最简单的方法是在尝试访问传递的变量值时编写测试类并为案例生成字节码,何时不会看到差异。

或者您可以联系作者:Aleksey Shipilev(遮阳)@ aleksey-shipilev

如果您真的想了解,我认为您需要阅读有关该内容的内容:

https://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/ http://www.javaworld.com/article/2076060/build-ci-sdlc/compiler-optimizations.html