日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java中已经存在了十几年的一个bug...

發(fā)布時(shí)間:2024/4/11 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java中已经存在了十几年的一个bug... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(diǎn)擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”

后臺(tái)回復(fù)”加群“獲取公眾號(hào)專屬群聊入口

來(lái)源:rrd.me/gfgTx

今天,分享一個(gè) JDK 中令人驚訝的 BUG,這個(gè) BUG 的神奇之處在于,復(fù)現(xiàn)它的用例太簡(jiǎn)單了,人肉眼就能回答的問(wèn)題,JDK 中卻存在了十幾年。經(jīng)過(guò)測(cè)試,我們發(fā)現(xiàn)從 JDK8 到 14 都存在這個(gè)問(wèn)題。

大家可以在自己的開(kāi)發(fā)平臺(tái)上試試這段代碼:

public class Hello {public void test() {int i = 8;while ((i -= 3) > 0);System.out.println("i = " + i);}public static void main(String[] args) {Hello hello = new Hello();for (int i = 0; i < 50_000; i++) {hello.test();}} }

再使用以下命令執(zhí)行:

java Hello

然后,就會(huì)看到這樣的輸出:

i = / i = / i = / i = / i = / i = / i = / i = / i = / i = / i = / i = /

當(dāng)然,在程序的開(kāi)始階段,還是能打印出正確的"i = -1"。

這個(gè)問(wèn)題最終 Huawei JDK 的兩名同事解決掉了,并且回合到社區(qū)。我這里大概講一下分析的思路。

首先,使用解釋執(zhí)行可以發(fā)現(xiàn),結(jié)果都是正確的,這就說(shuō)明,這基本上是 JIT 編譯器的問(wèn)題,然后通過(guò)-XX:-TieredCompilation關(guān)閉 C1 編譯,問(wèn)題同樣復(fù)現(xiàn),但是使用-XX:TieredStopAtLevel=3將 JIT 編譯停留在 C 階段,問(wèn)題就不復(fù)現(xiàn),這可以確定是 C2 的問(wèn)題了。

接下來(lái),一名同事立即猜想到這個(gè)"/"其實(shí)是('0'-1),剛好是字符零的 ascii 碼減掉 1。嗯,熟記 ascii 碼表的重要性就體現(xiàn)出來(lái)了。接下來(lái),就是找到 c2 中 int 轉(zhuǎn)字符的地方。關(guān)鍵點(diǎn),就在于這個(gè)字符'0',當(dāng)然這里要對(duì) C2 有足夠的了解,馬上就找到 c2 中字符轉(zhuǎn)化的方法(具體的代碼 ,請(qǐng)參考 OpenJDK 社區(qū)):

void PhaseStringOpts::int_getChars(GraphKit& kit, Node* arg, Node* char_array, Node* start, Node* end) {// ......// char sign = 0;Node* i = arg;Node* sign = __ intcon(0);// if (i < 0) {// sign = '-';// i = -i;// }{IfNode* iff = kit.create_and_map_if(kit.control(),__ Bool(__ CmpI(arg, __ intcon(0)), BoolTest::lt),PROB_FAIR, COUNT_UNKNOWN);RegionNode *merge = new (C) RegionNode(3);kit.gvn().set_type(merge, Type::CONTROL);i = new (C) PhiNode(merge, TypeInt::INT);kit.gvn().set_type(i, TypeInt::INT);sign = new (C) PhiNode(merge, TypeInt::INT);kit.gvn().set_type(sign, TypeInt::INT);merge->init_req(1, __ IfTrue(iff));i->init_req(1, __ SubI(__ intcon(0), arg));sign->init_req(1, __ intcon('-'));merge->init_req(2, __ IfFalse(iff));i->init_req(2, arg);sign->init_req(2, __ intcon(0));kit.set_control(merge);C->record_for_igvn(merge);C->record_for_igvn(i);C->record_for_igvn(sign);}// for (;;) {// q = i / 10;// r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...// buf [--charPos] = digits [r];// i = q;// if (i == 0) break;// }{// 略去和這個(gè)循環(huán)相對(duì)應(yīng)的代碼}// 略去很多代碼 }

可以看到,這里在中間表示階段引入了一個(gè)“i < 0"的判斷。主要就是那個(gè) CmpI 結(jié)點(diǎn),看起來(lái)這里的邏輯走錯(cuò)了,導(dǎo)致 i 明明小于 0,結(jié)果卻走到了大于 0 的分支,這樣,直接拿字符'0'與 i 求和的結(jié)果,就是錯(cuò)的了。

那這個(gè) CmpI 為什么會(huì)錯(cuò)呢?使用 c2visualizer 工具可以看到,在 GVN 階段,上面循環(huán)中的 CmpI 和這里引入的 CmpI 被合并了。GVN 的全稱是 Global Value Numbering,名字很高大上,其實(shí)就是表達(dá)式去重。例如:

上面的例子中,兩個(gè) CmpI 的輸入?yún)?shù)是完全相同的。都是變量 i 和整數(shù) 0,那么,這兩個(gè) CmpI 結(jié)點(diǎn)其實(shí)就是完全相同的。這樣的話,編譯器在做中間優(yōu)化的時(shí)候就會(huì)把這兩個(gè) CmpI 結(jié)點(diǎn)合并成一個(gè)。

到這里為止,其實(shí)還是沒(méi)問(wèn)題的。但接下來(lái),編譯器會(huì)對(duì)空的循環(huán)體做一些特別的變換,編譯器能直接計(jì)算出空循環(huán)體結(jié)束以后,i 的值是 -1,又發(fā)現(xiàn)空循環(huán)體什么都不做,所以,它干脆把 CmpI 的兩個(gè)參數(shù)都換成了 -1,以便于讓循環(huán)走不進(jìn)來(lái)——而且,編譯器再做一次常量傳播就可以把這個(gè) CmpI 徹底干掉了。但是,這里 CmpI 就有問(wèn)題了,這里強(qiáng)行搞成 False 讓循環(huán)不執(zhí)行,并且把 i 的值也直接變成循環(huán)結(jié)束的那個(gè)值。但剛才合并的那個(gè) CmpI 也被吃掉了。

這就導(dǎo)致,直接拿著 i = -1 這個(gè)值進(jìn)到了 i >= 0 的分支里了。所以修改也很簡(jiǎn)單,那就是在對(duì) CmpI 變換的時(shí)候,看看它還有沒(méi)有其他的 out,如果有,就復(fù)制一份出來(lái)。

這個(gè) BUG 的相關(guān) issue 和 patch 在這里:https://bugs.openjdk.java.net/projects/JDK/issues/JDK-8231988?filter=allissues

JBS 系統(tǒng)上沒(méi)有詳細(xì)的分析過(guò)程,只有最后的 patch,所以我把這個(gè)問(wèn)題寫了個(gè)總結(jié)發(fā)在這里??梢钥吹?#xff0c;即使是很簡(jiǎn)單的測(cè)試用例,在編譯器內(nèi)部也會(huì)經(jīng)歷各種復(fù)雜的變換和優(yōu)化。然后一些階段的優(yōu)化可能會(huì)影響后一個(gè)階段的,所以編譯器的 BUG 也往往晦澀。但反過(guò)來(lái)說(shuō),也很有意思。

想知道更多?描下面的二維碼關(guān)注我

后臺(tái)回復(fù)”加群“獲取公眾號(hào)專屬群聊入口

【精彩推薦】

  • 一文講透微服務(wù)下如何保證事務(wù)的一致性

  • 如何理解Linux中的零拷貝技術(shù)

  • 干貨!Java字節(jié)碼增強(qiáng)探秘

  • Java Agent初探

  • IO多路復(fù)用是什么意思

  • 當(dāng)我們?cè)谡務(wù)搩?nèi)存的時(shí)候,我們?cè)谡務(wù)撌裁?| 干貨

  • 分布式文件系統(tǒng)設(shè)計(jì),該從哪些方面考慮

  • 咱們從頭到尾說(shuō)一次Java垃圾回收

朕已閱?

總結(jié)

以上是生活随笔為你收集整理的Java中已经存在了十几年的一个bug...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。