输出-1在循环中变为斜线 [英] The output -1 becomes a slash in the loop
问题描述
令人惊讶的是,以下代码输出:
Surprisingly, the following code outputs:
/
-1
代码:
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);
}
}
}
我尝试了很多次以确定这种情况会发生多少次,但是不幸的是,最终还是不确定的,我发现-2的输出有时变成了一个句点.此外,我还尝试了删除while循环并输出-1,而没有任何问题.谁能告诉我为什么?
I tried many times to determine how many times this would occur, but, unfortunately, it was ultimately uncertain, and I found that the output of -2 sometimes turned into a period. In addition, I also tried to remove the while loop and output -1 without any problems. Who can tell me why?
JDK版本信息:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
推荐答案
可以使用openjdk version "1.8.0_222"
(用于我的分析),OpenJDK 12.0.1
(根据我的分析)可靠地复制(或不复制,取决于您想要的内容). (由Oleksandr Pyrohov提供)和OpenJDK 13(根据Carlos Heuberger).
This can be reliably reproduced (or not reproduced, depending on what you want) with openjdk version "1.8.0_222"
(used in my analysis), OpenJDK 12.0.1
(according to Oleksandr Pyrohov) and OpenJDK 13 (according to Carlos Heuberger).
我用-XX:+PrintCompilation
运行了足够的时间来获得两种行为,这就是区别.
I ran the code with -XX:+PrintCompilation
enough times to get both behaviours and here are the differences.
Buggy实现(显示输出):
Buggy implementation (displays output):
--- 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
正确运行(不显示):
--- 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)
我们可以注意到一个明显的不同.正确执行后,我们编译test()
两次.从头开始,然后再一次(大概是因为JIT注意到该方法有多热).在越野车中,test()
被编译(或反编译) 5 次.
We can notice one significant difference. With the correct execution we compile test()
twice. Once in the beginning, and once again afterwards (presumably because the JIT notices how hot the method is). In the buggy execution test()
is compiled (or decompiled) 5 times.
另外,使用-XX:-TieredCompilation
(可解释或使用C2
)或与-Xbatch
(强制编译在主线程中运行,而不是并行运行)一起运行,输出得到保证,并且经过30000次迭代可以打印出很多内容,因此C2
编译器似乎是罪魁祸首.通过运行-XX:TieredStopAtLevel=1
可以确认这一点,该功能禁用C2
并且不产生输出(在4级停止再次显示该错误).
Additionally, running with -XX:-TieredCompilation
(which either interprets, or uses C2
) or with -Xbatch
(which forces the compilation to run in the main thread, instead of parallelly), the output is guaranteed and with 30000 iterations prints out a lot of stuff, so the C2
compiler seems to be the culprit. This is confirmed by running with -XX:TieredStopAtLevel=1
, which disables C2
and doesn't produce output (stopping at level 4 shows the bug again).
在正确的执行中,首先使用第3级编译,然后是第4级.
In the correct execution, the method is first compiled with Level 3 compilation, then afterwards with Level 4.
在越野车执行过程中,先前的编译将被舍弃(made non entrant
),然后再次在3级(即C1
,请参见上一链接)上进行编译.
In the buggy execution, the previous compilations are discared (made non entrant
) and it's again compiled on Level 3 (which is C1
, see previous link).
因此,这绝对是C2
中的错误,尽管我不确定要返回到3级编译的事实是否会对其造成影响(以及为什么返回到3级,仍然存在许多不确定性)
So it definitely is a bug in C2
, although I'm not absolutely sure whether the fact that it's going back to Level 3 compilation affects it (and why is it going back to level 3, so many uncertainties still).
您可以使用以下代码行生成汇编代码,以更深入地钻入兔子孔(另请参见
You can generate the assembly code with the following line to go even deeper into the rabbit hole (also see this to enable assembly printing).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
在这一点上,我开始精疲力尽,当放弃以前的编译版本时,越野车的行为开始显现出来,但是我具备90年代的装配技巧,所以我会让一个比...更聪明的人我从这里拿走.
At this point I'm starting to run out of skills, the buggy behaviour starts to exhibit when the previous compiled versions are discarded, but what little assembly skills I have are from the 90's, so I'll let someone smarter than me take it from here.
It's likely that there is already a bug report about this, since the code was presented to the OP by someone else, and as all code C2 isn't without bugs. I hope this analysis has been as informative to others as it has been to me.
正如评论中古老的apangin所指出的,这是一个最近的错误.所有有兴趣和乐于助人的人都有很多义务:)
As the venerable apangin pointed out in the comments, this is a recent bug. Much obliged to all the interested and helpful people :)
这篇关于输出-1在循环中变为斜线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!