输出 -1 成为循环中的斜线
The output -1 becomes a slash in the loop
令人惊讶的是,以下代码输出:
/
-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,没有任何问题。谁能告诉我为什么?
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 的说法)。
我 运行 代码 -XX:+PrintCompilation
足够多次获得两种行为,这里是不同之处。
错误的实现(显示输出):
--- 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 次。
此外,运行 -XX:-TieredCompilation
(解释或使用 C2
) 或 与 -Xbatch
(强制在主线程中编译到 运行,而不是并行),输出是 gua运行teed 并且 30000 次迭代打印出很多东西,所以C2
编译器似乎是罪魁祸首。 运行 -XX:TieredStopAtLevel=1
证实了这一点,它禁用了 C2
并且不产生输出(停止在级别 4 再次显示错误)。
正确的执行方法是先用Level 3编译,再用Level 4编译。
在 buggy 执行中,之前的编译被丢弃 (made non entrant
) 并在级别 3 上再次编译 (即 C1
,参见之前的 link)。
所以它肯定是 C2
中的一个错误,虽然我不确定它回到 3 级编译的事实是否会影响它(以及为什么它回到 3 级,所以仍有许多不确定性)。
您可以使用以下行生成汇编代码以更深入地研究兔子洞(另请参阅 this 以启用汇编打印)。
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
在这一点上我开始运行技能不足,当以前的编译版本被丢弃时,错误行为开始表现出来,但我的组装技能是 90 年代的,所以我让比我聪明的人从这里拿走它。
很可能已经有关于此的错误报告,因为代码是由其他人提交给 OP 的,并且所有代码都是 C2 isn't without bugs。我希望这个分析对其他人和我一样有用。
正如 apangin 在评论中指出的那样,这是一个 recent bug。非常感谢所有感兴趣和乐于助人的人:)
不知道为什么 Java 会给出这样的随机输出,但问题出在您的连接中,在 for
循环中,i
的较大值会失败。
如果您将 String value = i + "";
行替换为 String value = String.valueOf(i) ;
,您的代码将按预期工作。
使用 +
将 int 转换为字符串的连接是原生的,可能存在错误(奇怪的是我们现在可能正在创建它)并导致此类问题。
注意:我将 for 循环中 i 的值减少到 10000,我没有遇到 +
连接的问题。
必须将此问题报告给 Java 利益相关者,他们可以就此发表意见。
编辑 我将for循环中i的值更新为300万,看到了一组新的错误如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at java.lang.Integer.getChars(Integer.java:463)
at java.lang.Integer.toString(Integer.java:402)
at java.lang.String.valueOf(String.java:3099)
at solving.LoopOutPut.test(LoopOutPut.java:16)
at solving.LoopOutPut.main(LoopOutPut.java:8)
我的Java版本是8.
老实说,这很奇怪,因为从技术上讲,该代码应该永远不会输出,因为...
int i = 8;
while ((i -= 3) > 0);
... 应始终导致 i
为 -1
(8 - 3 = 5;5 - 3 = 2;2 - 3 = -1)。更奇怪的是它从不在我的 IDE.
的调试模式下输出
有趣的是,当我在转换为 String
之前添加检查时,没有问题...
public void test() {
int i = 8;
while ((i -= 3) > 0);
if(i != -1) { System.out.println("Not -1"); }
String value = String.valueOf(i);
if (!"-1".equalsIgnoreCase(value)) {
System.out.println(value);
System.out.println(i);
}
}
只有两点良好的编码习惯......
- 而是使用
String.valueOf()
- 一些编码标准指定字符串文字应该是
.equals()
的目标,而不是参数,从而最大限度地减少 NullPointerExceptions。
我避免这种情况发生的唯一方法是使用 String.format()
public void test() {
int i = 8;
while ((i -= 3) > 0);
String value = String.format("%d", i);
if (!"-1".equalsIgnoreCase(value)) {
System.out.println(value);
System.out.println(i);
}
}
...本质上看起来 Java 需要一点时间来喘口气 :)
编辑:这可能完全是巧合,但打印出的值与 ASCII Table 之间似乎确实存在某种对应关系。
i
=-1
,显示的字符为/
(ASCII十进制值为47)
i
= -2
,显示的字符为.
(ASCII十进制值为46)
i
= -3
,显示的字符为-
(ASCII十进制值为45)
i
=-4
,显示的字符为,
(ASCII十进制值为44)
i
= -5
,显示的字符为+
(ASCII十进制值为43)
i
=-6
,显示的字符为*
(ASCII十进制值为42)
i
=-7
,显示的字符为)
(ASCII十进制值为41)
i
=-8
,显示的字符为(
(ASCII十进制值为40)
i
=-9
,显示的字符为'
(ASCII十进制值为39)
真正有趣的是ASCII十进制48处的字符是值0
和48 - 1 = 47(字符/
)等...
令人惊讶的是,以下代码输出:
/
-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,没有任何问题。谁能告诉我为什么?
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 的说法)。
我 运行 代码 -XX:+PrintCompilation
足够多次获得两种行为,这里是不同之处。
错误的实现(显示输出):
--- 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 次。
此外,运行 -XX:-TieredCompilation
(解释或使用 C2
) 或 与 -Xbatch
(强制在主线程中编译到 运行,而不是并行),输出是 gua运行teed 并且 30000 次迭代打印出很多东西,所以C2
编译器似乎是罪魁祸首。 运行 -XX:TieredStopAtLevel=1
证实了这一点,它禁用了 C2
并且不产生输出(停止在级别 4 再次显示错误)。
正确的执行方法是先用Level 3编译,再用Level 4编译。
在 buggy 执行中,之前的编译被丢弃 (made non entrant
) 并在级别 3 上再次编译 (即 C1
,参见之前的 link)。
所以它肯定是 C2
中的一个错误,虽然我不确定它回到 3 级编译的事实是否会影响它(以及为什么它回到 3 级,所以仍有许多不确定性)。
您可以使用以下行生成汇编代码以更深入地研究兔子洞(另请参阅 this 以启用汇编打印)。
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
在这一点上我开始运行技能不足,当以前的编译版本被丢弃时,错误行为开始表现出来,但我的组装技能是 90 年代的,所以我让比我聪明的人从这里拿走它。
很可能已经有关于此的错误报告,因为代码是由其他人提交给 OP 的,并且所有代码都是 C2 isn't without bugs。我希望这个分析对其他人和我一样有用。
正如 apangin 在评论中指出的那样,这是一个 recent bug。非常感谢所有感兴趣和乐于助人的人:)
不知道为什么 Java 会给出这样的随机输出,但问题出在您的连接中,在 for
循环中,i
的较大值会失败。
如果您将 String value = i + "";
行替换为 String value = String.valueOf(i) ;
,您的代码将按预期工作。
使用 +
将 int 转换为字符串的连接是原生的,可能存在错误(奇怪的是我们现在可能正在创建它)并导致此类问题。
注意:我将 for 循环中 i 的值减少到 10000,我没有遇到 +
连接的问题。
必须将此问题报告给 Java 利益相关者,他们可以就此发表意见。
编辑 我将for循环中i的值更新为300万,看到了一组新的错误如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at java.lang.Integer.getChars(Integer.java:463)
at java.lang.Integer.toString(Integer.java:402)
at java.lang.String.valueOf(String.java:3099)
at solving.LoopOutPut.test(LoopOutPut.java:16)
at solving.LoopOutPut.main(LoopOutPut.java:8)
我的Java版本是8.
老实说,这很奇怪,因为从技术上讲,该代码应该永远不会输出,因为...
int i = 8;
while ((i -= 3) > 0);
... 应始终导致 i
为 -1
(8 - 3 = 5;5 - 3 = 2;2 - 3 = -1)。更奇怪的是它从不在我的 IDE.
有趣的是,当我在转换为 String
之前添加检查时,没有问题...
public void test() {
int i = 8;
while ((i -= 3) > 0);
if(i != -1) { System.out.println("Not -1"); }
String value = String.valueOf(i);
if (!"-1".equalsIgnoreCase(value)) {
System.out.println(value);
System.out.println(i);
}
}
只有两点良好的编码习惯......
- 而是使用
String.valueOf()
- 一些编码标准指定字符串文字应该是
.equals()
的目标,而不是参数,从而最大限度地减少 NullPointerExceptions。
我避免这种情况发生的唯一方法是使用 String.format()
public void test() {
int i = 8;
while ((i -= 3) > 0);
String value = String.format("%d", i);
if (!"-1".equalsIgnoreCase(value)) {
System.out.println(value);
System.out.println(i);
}
}
...本质上看起来 Java 需要一点时间来喘口气 :)
编辑:这可能完全是巧合,但打印出的值与 ASCII Table 之间似乎确实存在某种对应关系。
i
=-1
,显示的字符为/
(ASCII十进制值为47)i
=-2
,显示的字符为.
(ASCII十进制值为46)i
=-3
,显示的字符为-
(ASCII十进制值为45)i
=-4
,显示的字符为,
(ASCII十进制值为44)i
=-5
,显示的字符为+
(ASCII十进制值为43)i
=-6
,显示的字符为*
(ASCII十进制值为42)i
=-7
,显示的字符为)
(ASCII十进制值为41)i
=-8
,显示的字符为(
(ASCII十进制值为40)i
=-9
,显示的字符为'
(ASCII十进制值为39)
真正有趣的是ASCII十进制48处的字符是值0
和48 - 1 = 47(字符/
)等...