3 回答
TA贡献1966条经验 获得超4个赞
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 注意到该方法有多热)。在有 bug 的情况下执行test()
会被编译(或反编译)5次。
此外,运行 with -XX:-TieredCompilation
(解释或使用C2
)或with -Xbatch
(强制编译在主线程中运行,而不是并行运行),输出是有保证的,并且 30000 次迭代会打印出很多东西,所以C2
编译器似乎成为罪魁祸首。这可以通过运行 with 来确认-XX:TieredStopAtLevel=1
,它会禁用C2
并且不会产生输出(在级别 4 停止会再次显示该错误)。
在正确的执行中,该方法首先使用第 3 级编译进行编译,然后再使用第 4 级编译。
在有错误的执行中,先前的编译将被丢弃 ( made non entrant
),并再次在第 3 级进行编译(即C1
,请参阅前面的链接)。
所以它肯定是 中的一个错误C2
,尽管我不确定它返回到 3 级编译的事实是否会影响它(以及为什么它返回到 3 级,仍然有很多不确定性)。
您可以使用以下行生成汇编代码,以更深入地了解兔子洞。
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
在这一点上,我开始耗尽技能,当以前的编译版本被丢弃时,错误行为开始表现出来,但我所拥有的一点汇编技能是来自 90 年代的,所以我会让比我更聪明的人来承担它从这里。
很可能已经有一个关于此的错误报告,因为代码是由其他人提交给OP的,并且所有代码C2都不是没有错误。我希望这个分析对其他人和我一样能提供丰富的信息。
TA贡献1793条经验 获得超6个赞
老实说,这很奇怪,因为从技术上讲,该代码不应该输出,因为......
int i = 8;
while ((i -= 3) > 0);
...应该始终导致i( -18 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1)。更奇怪的是,它从来没有在我的 IDE 的调试模式下输出。
有趣的是,当我在转换为 a 之前添加检查时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(字符/
),等等......
TA贡献1820条经验 获得超2个赞
不知道为什么 Java 会给出这样的随机输出,但问题在于您的串联对于循环i
内的较大值会失败for
。
如果将String value = i + "";
line 替换为String value = String.valueOf(i) ;
代码,则可以按预期工作。
用于+
将 int 转换为 string 的连接是本机的,可能存在错误(奇怪的是我们现在可能发现它)并导致此类问题。
注意:我将 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。
添加回答
举报