输出-1在循环中变为斜线

oka*_*ali 55 java string jit jvm while-loop

令人惊讶的是,以下代码输出:

/
-1
Run Code Online (Sandbox Code Playgroud)

代码:

public class LoopOutPut {

    public static void main(String[] args) {
        LoopOutPut loopOutPut = new LoopOutPut();
        for (int i = 0; i < 30000; i++) {
            loopOutPut.test();
        }

    }

    public void test() {
        int i = 8;
        while ((i -= 3) > 0) ;
        String value = i + "";
        if (!value.equals("-1")) {
            System.out.println(value);
            System.out.println(i);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

我尝试了很多次以确定这种情况会发生多少次,但是不幸的是,最终还是不确定的,并且我发现-2的输出有时变成了一个周期。另外,我还尝试了删除while循环并输出-1,而没有任何问题。谁能告诉我为什么?


JDK版本信息:

public class LoopOutPut {

    public static void main(String[] args) {
        LoopOutPut loopOutPut = new LoopOutPut();
        for (int i = 0; i < 30000; i++) {
            loopOutPut.test();
        }

    }

    public void test() {
        int i = 8;
        while ((i -= 3) > 0) ;
        String value = i + "";
        if (!value.equals("-1")) {
            System.out.println(value);
            System.out.println(i);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

Kay*_*man 36

可以使用openjdk version "1.8.0_222"(用于我的分析),OpenJDK 12.0.1(根据Oleksandr Pyrohov)和OpenJDK 13(根据Carlos Heuberger)可靠地复制(或复制,具体取决于您想要的内容)。

我运行了-XX:+PrintCompilation足够的时间来获得两种行为的代码,这就是区别。

Buggy实现(显示输出):

 --- Previous lines are identical in both
 54   17       3       java.lang.AbstractStringBuilder::<init> (12 bytes)
 54   23       3       LoopOutPut::test (57 bytes)
 54   18       3       java.lang.String::<init> (82 bytes)
 55   21       3       java.lang.AbstractStringBuilder::append (62 bytes)
 55   26       4       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
 55   20       3       java.lang.StringBuilder::<init> (7 bytes)
 56   19       3       java.lang.StringBuilder::toString (17 bytes)
 56   25       3       java.lang.Integer::getChars (131 bytes)
 56   22       3       java.lang.StringBuilder::append (8 bytes)
 56   27       4       java.lang.String::equals (81 bytes)
 56   10       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   made not entrant
 56   28       4       java.lang.AbstractStringBuilder::append (50 bytes)
 56   29       4       java.lang.String::getChars (62 bytes)
 56   24       3       java.lang.Integer::stringSize (21 bytes)
 58   14       3       java.lang.String::getChars (62 bytes)   made not entrant
 58   33       4       LoopOutPut::test (57 bytes)
 59   13       3       java.lang.AbstractStringBuilder::append (50 bytes)   made not entrant
 59   34       4       java.lang.Integer::getChars (131 bytes)
 60    3       3       java.lang.String::equals (81 bytes)   made not entrant
 60   30       4       java.util.Arrays::copyOfRange (63 bytes)
 61   25       3       java.lang.Integer::getChars (131 bytes)   made not entrant
 61   32       4       java.lang.String::<init> (82 bytes)
 61   16       3       java.util.Arrays::copyOfRange (63 bytes)   made not entrant
 61   31       4       java.lang.AbstractStringBuilder::append (62 bytes)
 61   23       3       LoopOutPut::test (57 bytes)   made not entrant
 61   33       4       LoopOutPut::test (57 bytes)   made not entrant
 62   35       3       LoopOutPut::test (57 bytes)
 63   36       4       java.lang.StringBuilder::append (8 bytes)
 63   18       3       java.lang.String::<init> (82 bytes)   made not entrant
 63   38       4       java.lang.StringBuilder::append (8 bytes)
 64   21       3       java.lang.AbstractStringBuilder::append (62 bytes)   made not entrant
Run Code Online (Sandbox Code Playgroud)

正确运行(无显示):

 --- Previous lines identical in both
 55   23       3       LoopOutPut::test (57 bytes)
 55   17       3       java.lang.AbstractStringBuilder::<init> (12 bytes)
 56   18       3       java.lang.String::<init> (82 bytes)
 56   20       3       java.lang.StringBuilder::<init> (7 bytes)
 56   21       3       java.lang.AbstractStringBuilder::append (62 bytes)
 56   26       4       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
 56   19       3       java.lang.StringBuilder::toString (17 bytes)
 57   22       3       java.lang.StringBuilder::append (8 bytes)
 57   24       3       java.lang.Integer::stringSize (21 bytes)
 57   25       3       java.lang.Integer::getChars (131 bytes)
 57   27       4       java.lang.String::equals (81 bytes)
 57   28       4       java.lang.AbstractStringBuilder::append (50 bytes)
 57   10       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   made not entrant
 57   29       4       java.util.Arrays::copyOfRange (63 bytes)
 60   16       3       java.util.Arrays::copyOfRange (63 bytes)   made not entrant
 60   13       3       java.lang.AbstractStringBuilder::append (50 bytes)   made not entrant
 60   33       4       LoopOutPut::test (57 bytes)
 60   34       4       java.lang.Integer::getChars (131 bytes)
 61    3       3       java.lang.String::equals (81 bytes)   made not entrant
 61   32       4       java.lang.String::<init> (82 bytes)
 62   25       3       java.lang.Integer::getChars (131 bytes)   made not entrant
 62   30       4       java.lang.AbstractStringBuilder::append (62 bytes)
 63   18       3       java.lang.String::<init> (82 bytes)   made not entrant
 63   31       4       java.lang.String::getChars (62 bytes)
Run Code Online (Sandbox Code Playgroud)

我们可以注意到一个明显的不同。正确执行后,我们将编译test()两次。从头开始,然后再一次(大概是因为JIT注意到该方法有多热)。在越野车中,执行test()被编译(或反编译)5次。

此外,与运行-XX:-TieredCompilation(这两种解释,或使用C2-Xbatch(这迫使编译在主线程中运行,而不是平行),输出保证,并与30000反复打印出了很多东西,所以C2编译器似乎成为罪魁祸首。通过运行可以确认这一点-XX:TieredStopAtLevel=1,它可以禁用C2也不产生输出(在4级停止再次显示该错误)。

在正确执行中,首先使用3级编译来编译该方法,然后再使用4 编译。

在越野车执行中,先前的编译将被舍弃(made non entrant),然后再次在Level 3上进行编译(即C1,请参见前面的链接)。

因此,这绝对是一个错误C2,尽管我不确定要返回到3级编译的事实是否会对其造成影响(以及为什么返回到3级,仍然存在很多不确定性)。

您可以生成以下行的汇编代码去更深进了兔子洞(另见使装配打印)。

java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
Run Code Online (Sandbox Code Playgroud)

在这一点上,我开始精疲力尽,当放弃以前的编译版本时,越野车的行为开始显现出来,但是我具备90年代的少量组装技能,所以我会让比我聪明的人来使用它从这里。

很可能已经有关于此的错误报告,因为代码是由其他人提供给OP的,并且所有代码C2都没有错误。我希望这种分析对我一样有益。

正如古老的apangin在评论中指出的,这是一个最近的bug。所有有兴趣和乐于助人的人都有义务:)

  • 我只是偶然地注意到了这个话题。为了确保我能看到问题,请使用@mentions或添加#jvm标记。好的分析,顺便说一句。这确实是一个C2编译器错误,仅在几天前修复-[JDK-8231988](https://bugs.openjdk.java.net/browse/JDK-8231988)。 (7认同)