3 回答

TA貢獻1966條經(jīng)驗 獲得超4個贊
openjdk version "1.8.0_222"這可以通過(在我的分析中使用)、OpenJDK 12.0.1(根據(jù) Oleksandr Pyrohov)和 OpenJDK 13(根據(jù) Carlos Heuberger)可靠地重現(xiàn)(或不重現(xiàn),取決于你想要什么)。
我運行了代碼-XX:+PrintCompilation足夠多的時間來獲得這兩種行為,以下是差異。
有缺陷的實現(xiàn)(顯示輸出):
?--- 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)
我們可以注意到一個顯著的差異。通過正確的執(zhí)行,我們編譯了test()
兩次。開始時一次,之后再一次(大概是因為 JIT 注意到該方法有多熱)。在有 bug 的情況下執(zhí)行test()
會被編譯(或反編譯)5次。
此外,運行 with?-XX:-TieredCompilation
(解釋或使用C2
)或with?-Xbatch
(強制編譯在主線程中運行,而不是并行運行),輸出是有保證的,并且 30000 次迭代會打印出很多東西,所以C2
編譯器似乎成為罪魁禍首。這可以通過運行 with 來確認-XX:TieredStopAtLevel=1
,它會禁用C2
并且不會產(chǎn)生輸出(在級別 4 停止會再次顯示該錯誤)。
在正確的執(zhí)行中,該方法首先使用第 3 級編譯進行編譯,然后再使用第 4 級編譯。
在有錯誤的執(zhí)行中,先前的編譯將被丟棄 (?made non entrant
),并再次在第 3 級進行編譯(即C1
,請參閱前面的鏈接)。
所以它肯定是 中的一個錯誤C2
,盡管我不確定它返回到 3 級編譯的事實是否會影響它(以及為什么它返回到 3 級,仍然有很多不確定性)。
您可以使用以下行生成匯編代碼,以更深入地了解兔子洞。
java?-XX:+PrintCompilation?-Xbatch?-XX:+UnlockDiagnosticVMOptions?-XX:+PrintAssembly?LoopOutPut?>?broken.asm
在這一點上,我開始耗盡技能,當(dāng)以前的編譯版本被丟棄時,錯誤行為開始表現(xiàn)出來,但我所擁有的一點匯編技能是來自 90 年代的,所以我會讓比我更聰明的人來承擔(dān)它從這里。
很可能已經(jīng)有一個關(guān)于此的錯誤報告,因為代碼是由其他人提交給OP的,并且所有代碼C2都不是沒有錯誤。我希望這個分析對其他人和我一樣能提供豐富的信息。

TA貢獻1793條經(jīng)驗 獲得超6個贊
老實說,這很奇怪,因為從技術(shù)上講,該代碼不應(yīng)該輸出,因為......
int i = 8;
while ((i -= 3) > 0);
...應(yīng)該始終導(dǎo)致i( -18 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1)。更奇怪的是,它從來沒有在我的 IDE 的調(diào)試模式下輸出。
有趣的是,當(dāng)我在轉(zhuǎn)換為 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()
一些編碼標(biāo)準指定字符串文字應(yīng)該是 的目標(biāo)
.equals()
,而不是參數(shù),從而最大限度地減少 NullPointerExceptions。
我避免這種情況發(fā)生的唯一方法是使用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之間似乎確實存在一些對應(yīng)關(guān)系。
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條經(jīng)驗 獲得超2個贊
不知道為什么 Java 會給出這樣的隨機輸出,但問題在于您的串聯(lián)對于循環(huán)i
內(nèi)的較大值會失敗for
。
如果將String value = i + "";
line 替換為String value = String.valueOf(i) ;
代碼,則可以按預(yù)期工作。
用于+
將 int 轉(zhuǎn)換為 string 的連接是本機的,可能存在錯誤(奇怪的是我們現(xiàn)在可能發(fā)現(xiàn)它)并導(dǎo)致此類問題。
注意:我將 for 循環(huán)中 i 的值減少到 10000,并且沒有遇到+
連接問題。
這個問題必須報告給 Java 利益相關(guān)者,他們可以對此發(fā)表意見。
編輯我將 for 循環(huán)中 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。
添加回答
舉報