看板 java 關於我們 聯絡資訊
抱歉,"靈異現象"一詞是敝公司內部在用的 主要是講各種 執行不如預期 通常,有很大機率,會被人說"那是你自己寫程式不小心" 比如,程式在不該當的地方當了 但怎麼查也查不到原因 其實原因是更早之前使用了不存在或已刪除的變數 (這狀況在 java 還沒碰過;我是舉 C 的例子) 因為無效指標會讓程式流程跑到亂碼 也可能破壞堆疊;而且不一定"馬上"當 不過這次的例子比較奇怪 主管已經追一個禮拜了 (幸好不是發生在我身上,不然一定罵死我又不相信我 發生在他身上,他則說"解決了有賞"XD) public void run() { long __lCurrentTime = 1466154837; long __lTimeout = __lCurrentTime + 6; boolean __result = false; // 中斷點1:請在下一行放置一個中斷點 int __count = 0; for ( ; __lCurrentTime<=__lTimeout ; __lCurrentTime++) { __result = __lCurrentTime<=__lTimeout; __count++; } // 中斷點2:請在下一行放置一個中斷點 } 這次的例子是上面的小程式 中斷點 2 永遠執行不到 而檢查 __result, 也竟然永遠為 true 自己直接把程式放入自己的小專案當然沒問題 我們這個是放在敝公司的專案中 而奇怪的是,它還挑 build machine 這段 code 要在特定機器上 build 出 APK 才會執行不完 其他機器去 build, 則不會執行出問題 (而我們信任自己的 build machine 啊,那是裝好後只用來 build 程式的一台專用機器) 重灌 build machine 嗎?也不對,因為不只一台會出問題 (會出問題的就一直 build 出問題) 程式的邏輯很簡單(雖然不實用;因為它是專為了重製問題,簡化出來的 code) 同事因此做了個猜測:堆疊炸了 如果堆疊炸了,那當然程式就不必談邏輯了 但這段程式是位在另一個 thread, 同事加大 thread stack new Thread(null, null, TAG, 2*1024*1024) 沒有用 主管現在用另一個方法迴避問題 把原來的 long, 故意 cast 成 double 這樣是很無聊啦,但 compare 結果會正確了!! (如果是堆疊炸了,應該要減少使用自動變數才對 奇奇怪怪的迴避方法未必能解決問題吧!) 同事說,這種奇怪的問題,未必能用以前 C/C++ 時的邏輯去猜想 可能要更深入 byte code 去推斷 請問有沒有人解過類似的問題 (這不像 java 語法問題,而是 java 環境使用上的問題) 謝謝 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 60.251.197.55 ※ 文章網址: https://www.ptt.cc/bbs/java/M.1466485086.A.EF4.html
KekeMonster: 是我木眼嗎,result 本來就該是 true,false 才是靈 06/21 18:18
KekeMonster: 異現象吧? 06/21 18:18
__lCurrentTime 會漸漸變大,而它比起另一個變數,只大6 因此這個 forloop 執行六次左右就該結束 結束時 __lCurrentTime<=__lTimeout 這個判斷條件應為 false 而 __result 應該不會被更新到 目前的問題就是不結束,__result 永遠為 true 我應該沒寫錯
qrtt1: 不要把找不出 root cause 當靈異現象啊 @o@ 06/21 19:08
我常在板上挨罵就算了,他們可資深得不得了 工研院出來的,Trace 過整個 Unix, 我會懷疑他們在 java 上的功力,畢竟碰沒多久 但不會懷疑他們在 c/c++, multi-thread 的功力 (因為也曾是 multithread 講師) 因此比如變數被另一個 thread 干擾,這個也排除了 我自己前陣子是解掉一個,原因就是 multi-thread 變數 a 的值,我一直 trace 都是我要的,突然一轉眼就變了 但是同事避這個問題的方法很簡單:他沒必要共享變數,他的是 local 變數 既然變數沒被另一個 thread 參考到,那值就沒有變的理由 那為什麼會出現像 boolean a = (5 <= 3); 結果 a 為 true, 這樣很根本上的錯誤呢? ------------------- 問題可能解掉了 方法是把一個四千多行的函式縮小,拆成數個 因此目前我們只能認為,這也是 java 的極限 (我不是說不能寫四千多行喔,別自己去寫一個四千多行的來打我臉,不是這樣喔) 麻煩的就是,既然炸了也好歹打聲招呼,丟個 fatal 出來 都沒有啊,默默的... 那我們就有一堆不確定的猜測,大量的測試 操死一堆同事... ※ 編輯: HuangJC (60.251.197.55), 06/21/2016 19:26:31
KekeMonster: result 已經在 loop 裡被更新 6 次 true 了怎麽會是 06/22 00:21
KekeMonster: false 06/22 00:21
dennisxkimo: 我怎麼看都不覺得結果是flase 06/22 11:44
HuangJC: 不會是false,但也不該被執行到 06/22 11:58
HuangJC: 我們是因為奇怪它為什麼被執行 第七次以上 06/22 11:59
HuangJC: 大家還是在正常邏輯裡打轉,那不可能看懂我這篇啊... 06/22 12:00
dennisxkimo: 我看起來是0> 1> 2> 3> 4> 5> 6 七次 06/22 12:06
KekeMonster: 哈 對是 7 次 06/22 12:09
KekeMonster: 我覺得你為了呈現現象,把範例程式碼過度簡化到不會 06/22 12:11
KekeMonster: 有錯了吧 06/22 12:11
KekeMonster: 推 qrtt1 大所說的,我覺得比較像是邏輯性的錯誤造 06/22 12:16
KekeMonster: 成無窮迴圈,找出 root cause 吧 06/22 12:16
bitlife: java method的max code size是有64KB的上限,但compiler多 06/22 12:30
bitlife: 半會警告,4000行過了沒有難以確定,但寫這麼長確定不太好 06/22 12:30
bitlife: 除錯及維護 06/22 12:30
HuangJC: 那我再說一次,不是跑了七次,是 endless,大家還是覺得 06/22 13:05
HuangJC: 邏輯性錯誤? 06/22 13:05
HuangJC: 當跑了二十次,我們就懷疑其布林值,這時還為true就怪了 06/22 13:08
HuangJC: 程式有簡化,但簡化的版本足以重製問題… 06/22 13:09
HuangJC: 我不是在表達邏輯而已,而是就這樣的code就會有endless l 06/22 13:11
HuangJC: oop 06/22 13:11
dennisxkimo: "不結束,__result永遠為true",for loop 無關resut吧 06/22 14:08
ssccg: 有錯和沒錯的版本bytecode長一樣? 以你的說法看起來跟build 06/22 16:00
ssccg: 環境有關,跟執行環境沒關? 06/22 16:01
ssccg: 那去研究stack memory好像方向錯誤 06/22 16:03
HuangJC: For loop 是否結束,決定於那個布林算式,那是和result一 06/22 16:17
HuangJC: 模一樣的算式 06/22 16:17
我文章從頭到尾都說和build machine 有關啊,換一台就不會 從前我做 embedded system 時 用的 cross compiler 很讓人懷念 那是一個只要一模一樣的 source code 進去 build 出來的 bin 就會一模一樣的 compiler 因此,每當我拿到一台新的電腦,重架起 compiler 時 第一件事,就是 build 一下我們最新的 release 拿到結果後做 file compare, 一定要一模一樣 若沒一模一樣,就一定有錯 又比如,加入新 model, 新功能 而同一個 code 要 build 舊 model code 已經有變了,要證明沒影響舊 model 也是 build 一版出來做 file compare 像現在這個問題,特定 build machine 有問題 其實我是很想說"就那台 build machine 自己有問題啦" 但不只一台會 因此如果沒找到問題,有個合理假設 換台機器 build 仍然只會被認為是頭痛醫頭腳痛醫腳而已 ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 16:34:28
bitlife: 作業系統,CPU,JDK版本等都一模一樣嗎? 這當然有可能是遇 06/22 16:37
bitlife: compiler本身的錯誤 06/22 16:38
__lCurrentTime = 1466154837; __lTimeout = __lCurrentTime + 6 跑數次後, __lTimeout 沒變 __lCurrentTime 變成 1446154850 然後 __result = __lCurrentTime<=__lTimeout; 這個 __result 依然是 true 這樣大家感覺到問題沒有? 當然我自己不會這樣寫程式 我認為如果是記憶體錯亂,那麼 for loop 裡的布林值和算式裡的布林值,就未必結果相同 真的要龜毛,必需這樣寫 for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) { Log.v(TAG, __result); __count++; } 也就是一定要和 forloop 的判斷式,用同一個 看到底是 boolean 不如預期 還是明明為 false 了但卻不往外跳 不管哪一個,其實都是執行不如預期 但我覺得這樣會更精準點 這已經不是語言邏輯層次的問題,我其實見過很多次 文章前面也說了:當記憶體配置出問題,那就見怪不怪了 java 用的是 GC,而不像 C 要由工程師自己去管理記憶體,new & delete 因此在 GC 極強的記憶體管理下 我其實還不太清楚怎麼造出以前的狀況 在 C,這種問題可以說是層出不窮,都是要去檢討有沒有做了違犯規定的存取.. ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 16:45:12
ssccg: 所以bytecode到底一不一樣... 06/22 16:45
HuangJC: bytecode 是指 build 出來的結果嗎?不一樣 06/22 16:45
ssccg: compiler有可能就真的有bug啊... 06/22 16:45
HuangJC: android compiler 可以做到一模一樣嗎?我試試 06/22 16:46
ssccg: android build過程有幾個step,至少先看一下是從.class就不 06/22 16:50
ssccg: 一樣,還是dex不一樣,還是哪邊...才知道問題在哪步吧 06/22 16:50
HuangJC: 我自己的電腦 build 兩次,就不會一模一樣了 06/22 16:53
HuangJC: 所以這種 compiler 是無法這樣驗證的 06/22 16:53
kiwatami: 我覺得問題不在機器 而是程式裡面有不夠嚴謹的判斷 06/22 16:58
kiwatami: 而機器的差異導致這個 bug 產生 06/22 16:58
kiwatami: 有沒有試著在迴圈內 print 相關的值? 06/22 16:58
kiwatami: 看看值的變化在兩台機器上有什麼不同? 06/22 16:58
kiwatami: 並且確定每次值的異動是不是都有執行到 06/22 16:58
kiwatami: 感覺你的判斷式跟時間有關 如果加上 thread.sleep 06/22 16:58
kiwatami: 有沒有可能導致結果不如預期? 06/22 16:58
kiwatami: 例如時間差太大跑進了其他判斷式 導致 r 或 c 沒被更新 06/22 16:58
kiwatami: 然後這個迴圈本來就會執行七次 r 也會是 true 06/22 16:58
kiwatami: 如果 r 不是 true 就根本進不去迴圈了 06/22 16:58
kiwatami: 你仔細看 迴圈的條件跟你的 r 值條件是一樣的 06/22 16:58
HuangJC: 為什麼和時間有關?又沒有 time api.. 06/22 17:07
ssccg: 最後的apk當然會不一樣,但是.class和.dex我試過同電腦相同 06/22 17:09
dennisxkimo: for結束跳出後 {}內result還是true很正常 06/22 17:11
沒結束,沒跳出;我再強調一次,這變成 endless loop
HuangJC: ... 謝謝大家,因為問題已解,所以我也重製不了了... 06/22 17:11
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:12:08
dennisxkimo: __result不能拿來判斷迴圈結束條件的狀態 06/22 17:12
HuangJC: 但如果迴圈死不結束,它就有意義了 06/22 17:14
dennisxkimo: 死不結束 要檢查你結束條件設的變數 不是result 06/22 17:19
可是 result 和結束條件一模一樣啊 > → kiwatami: 你仔細看 迴圈的條件跟你的 r 值條件是一樣的 是的,我知道;故意的 就是一模一樣,所以我們現在在這裡各說各話了... ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:22:41
dennisxkimo: for條件內的A<=B 跟{}內的C=A<=B的C不同 06/22 17:21
是的,你和我一樣龜毛 所以如果讓我主導,我主張這樣寫 for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) { 分成兩行,就要小心有別的機會被修改變數 我再申明一點: 這不是示意 code, 我們就只把這麼簡單的 code 放在程式裡 就變成 endless loop 所以誰還能偷改變數? 邏輯內已經沒什麼好檢討,要檢討的是邏輯外... ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:25:30
HuangJC: 還有一點,因為"一定要特定機器 build 才會出錯" 06/22 17:28
HuangJC: 所以,無法在自己機器上步進執行.. 06/22 17:28
HuangJC: 只能不斷的設計 log;所有變數,我們都監看了 06/22 17:28
HuangJC: 當我們 trace 到,current 值'遠'大於 timeout值時 06/22 17:29
HuangJC: 真的,無法解釋.. 06/22 17:29
HuangJC: unsigned 的溢位我也假設過;所以我們才把布林值記下來 06/22 17:30
HuangJC: 不然不必加上那一行的.. 06/22 17:30
dennisxkimo: 不要被改結束條件就for(int i=0;i<=6;i++){},不行嗎? 06/22 17:33
行啊,但那就不是我們的程式了 同事只說"這片段可以重製 BUG" 我們是要找問題 最後,我們同意這是堆疊爆了的現象 他說完後,誰讚成,誰反對?
dennisxkimo: 不然當curtime某原因不小心大於long.max就... 06/22 17:34
所以我們的實驗方法是有注意到這點的 ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:36:38
KekeMonster: " 分成兩行,就要小心有別的機會被修改變數" 寫成一 06/22 17:36
KekeMonster: 行就沒機會? 你程式最後被編譯成 bytecode 後變成幾 06/22 17:36
KekeMonster: 個指令? 06/22 17:36
HuangJC: 盡人事而已,我沒更好的工具了.. 06/22 17:37
dennisxkimo: 假設Timeout保證不變,應該是觀察__lCurrentTime值 06/22 17:37
有 log 它沒錯
HuangJC: 我們對 bytecode 會有的現象真的不熟.. 06/22 17:37
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:38:02 題外話,在以前寫 C 時,我碰過另一個靈異現象 void F2() { a = 5; } void F1() { F2(); a = 6; a = 7; . . . } 如上,F2 是幫忙設一些初始值 接下去 F1 的片段是開始修改那些值 可是我發現那些值一直跳回來 比如上例的 a, 明明後續設為 6, 設為 7 但監控它會發現它一直是 5 我全域搜尋 a 何時被改為 5,只有 F2 裡有做這件事 後來忍不住了,在 F2 開頭下一個中斷點 結果發現,F1 "每執行一行",就會跳去執行 F2 一次 程式裡完全看不出來,但執行結果就是這樣 那次我加班到凌晨都解不掉 幸好第二天,主管說那個案子不做了 我真的感覺莫名其妙 不管重開機,clean build 種種能把環境弄乾淨的方法我都試了 但我就是不知道,誰奪取了控制權,硬去執行 F2 的 那種感覺就像陷入了單步中斷 debugger 就會做這種事 但那是 VC 的特權,誰搶去做了? ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:49:37
dennisxkimo: 你是用debug 一一觀察每次迴圈 各變數變化嗎? 06/22 17:59
dennisxkimo: 還是在迴圈內外插入print之類來看數值? 06/22 18:00
這要講兩支程式,一支是線上實際出貨的 這個有看所有變數 另外一個是為了縮小測試範圍,所以簡化(但仍然可以重製 bug) 公開在板上的是這支程式 這支程式裡已經沒有 log 了 我也不可能用步進執行 因為,一但步進執行,就是在我的機器上重 build (不重 build 但又可以步進執行,以前 code view 玩過,現在在 android 我還沒試過) 而我們強調是'那一台特定 build machine 才會有問題' 公開的這一支,同事只說,永遠執行不到第二個中斷點 endless loop 這句話,和實際出貨的程式倒是一樣 QA 測到的就是迴圈跑不完;但比較值已經遞增到極大了,為什麼還不結束 loop? (比較值及被比較值,兩者都有 log) 我是很想參戰啦,但我沒重製過問題 我參戰的必要條件是:讓我可以操作 build machine,任意 build 我改的 code 這點,沒法子 大家煩得要死,不給我玩 -------------- 我們出貨的程式,不是任意 RD 電腦 build 出來都可以出 而是特定的 build machine 它一連串的批次動作,從取 code, build, 到包裝成出貨 package 放至特定子目錄 (子目錄名稱和時間日期相關)都是自動化的 我無法把我的 code 塞給它,除非我 check in code 他們不給我玩啦.. 所以,謝謝大家,警察可以出來洗地了... ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 18:15:49
kiwatami: 喔 因為你特別用 time millis 當範例 我才以為你很直覺 06/22 18:15
kiwatami: 的使用你原本的原始碼使用的判斷式當例子 才會猜你原本 06/22 18:15
kiwatami: 的判斷式是不是跟時間有關 06/22 18:15
kiwatami: 不過既然你知道是一樣的 06/22 18:15
kiwatami: 怎麼會認為他跑不了第七次? 06/22 18:15
kiwatami: 是跑不了第八次才對 06/22 18:15
kiwatami: 如果是你說的那個什麼堆疊爆了... 06/22 18:15
kiwatami: 這範例才 10 行不到 不太可能吧 06/22 18:15
但我有說,這段程式,要放在我們程式內 我不想怪你沒爬文耶,我是來請教大家的,要有禮貌 :P
kiwatami: print currenttime 會超過 timeout? 還是永遠等於? 06/22 18:15
會超過 :P ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 18:18:22
ssccg: 這次沒得試了,下次遇到再說吧,我是覺得比較可能是編出來 06/22 18:35
ssccg: 執行檔就是錯的,不然stack爆了再把long cast成double反而 06/22 18:36
ssccg: 會對不太合理 06/22 18:37
HuangJC: 我多少表達個參戰的決心,唉 06/22 18:38
HuangJC: 講話要先大聲才有機會 06/22 18:38
HuangJC: 我很有想法,我覺得log太少了 06/22 18:39
for ( ; ; __lCurrentTime++) { __result = (__lCurrentTime<=__lTimeout); Log.v();//__result, __lCurrentTime, __lTimeout 三個都要看 //一次就能讓大家標定問題 if ( !__result ) break; } 這樣更龜毛 問題應該可以限縮至"為什麼 long compare 出錯了" ※ 編輯: HuangJC (60.251.197.55), 06/22/2016 19:09:55
HuangJC: 我一向不喜歡同樣的事做兩次,然後預設它們結果一樣 06/22 19:10
HuangJC: 我喜歡的 code style,就是只做一次,把它存起來用兩次 06/22 19:10
HuangJC: 這樣爭議小很多.. 06/22 19:11
HuangJC: 比如 multi-thread 變數瞬變,我踢的鐵板也夠多了.. 06/22 19:12
kiwatami: 我沒爬完整 我錯了 我自己去角落玩沙... 06/22 19:51
HuangJC: :P 我自己安全過關就好.. 06/23 00:14
realmeat: c compile出問題並不是啥新鮮事 06/23 11:45
realmeat: 通常都要追bytecode才看的出來 06/23 11:45
HuangJC: byte code 要怎樣追?以前寫 VC 時,可以輕易調出組合語 06/23 12:41
HuangJC: 言,以及監看所有暫存器;現在在 Android Studio 下,也 06/23 12:41
HuangJC: 可以輕易看到 bytecode 嗎? 06/23 12:41
Chikei: 拿artifact出來直接看編出來的bytecode不同機器是不是一樣 06/24 14:56
eieio: 1446154850 本來就比 1466154837+6 要小啊 07/01 12:41