昨天JAVA圈,美團曝出了一道變態級面試題:為什么棧溢出后線程沒有崩潰?為什么這段代碼會永遠執行下去?
我的幾個交流群、VIP群,爭論不休,看大家都是在Java層找答案。很明顯,這個問題的答案不在Java層,接下來咱們分析下這個問題,然后一起去找答案,爭取下次被問到,一舉擊潰面試官的心理防線:偶滴乖乖,是這道題難度太小還是我太菜?
我會按照看到這個問題我是如何分析如何做實驗如何得出結論,層層遞進展開,你讀起來應該會越來越嗨皮!瓦特?你沒嗨皮?你是不是沒看懂哦?
01
我的第一反應
我的第一反應是catch Error會永遠執行,那catch Exception呢?接下來上代碼
看運行結果
會報棧OOM
我腦海中馬上想到兩個問題:
1、這個棧深度是多少時拋出的?
2、為什么catch Exception會拋出?
怎么查看棧深度呢?JVM提供了相關方法嗎?木有!我們通過linux命令來統計
報棧OOM時,棧的深度是1024,這個數字大家記一下,后面還會講到,很關鍵!
接下來第二個問題,為什么catch Exception會拋出OOM?其實對于這段代碼,這個問題就是坑,因為棧溢出拋出的是StackOverflowError,你catch Exception是無效的,等同于
什么?你不信?你把代碼改成這樣,看看catch代碼塊會不會執行
02
第二段探索
研究完了我的第一反應并得到答案以后,我就開始了我的第二段探索:這個Java程序能夠無限執行,這個能力是操作系統自帶的還是JVM自己開發出來的?
我們來看看操作系統是否具備讓程序永遠運行的能力。怎么測試呢?需要你懂一點Linux多線程相關的東西。不懂也沒關系,看我演示的現象及結論即可。底層內功重不重要,從這里可見一斑。
可以看到,Linux系統默認是不支持程序無限執行的
為什么最后會報段錯誤呢?因為Linux系統創建線程,默認的棧大小是8M,程序無限遞歸把8M用光了,但是程序還不會終止,不自覺會用到8M之外的內存
這里面還有個隱含知識點,棧圖,沒有這個基礎你可能很難理解上面講的,放個圖幫助你理解
03
第三段探索
既然Linux系統沒有提供這樣的能力,然JVM能如此,大膽猜想可能的原因:
1、JVM改變了系統默認棧大小8M,可能改成了很大很大。如果是這樣,那我們看這段程序的無限執行其實是假象,如果讓它一直跑,跑很久很久,可能它就over了。
2、JVM內部做了優化,比如棧幀回溯、遞歸內聯、尾遞歸優化。驗證這個的時候還得考慮方法執行的兩個階段:解釋執行,執行JIT及時編譯后的代碼。是真滴復雜!
我們開始看下JVM的主線程有沒有改線程棧的默認大小,改成了多少。這個要怎么看?單步調試openjdk
JVM把改成了1M。
JVM的虛擬機棧是在操作系統的線程棧上進行拓展的,Linux系統的默認線程棧是8M,如果是遞歸調用,一下就跑完了。但是美團的這段程序,很明顯跑了很久都沒結束,所以這種情況pass,只剩最后一種情況。
04
第四段探索
前面提到了兩個關鍵的東西:棧深度1024,如果程序遞歸調用把線程棧用光了會報段錯誤。這兩個玩意這里都要用上。開始探索
咱們先就之前的三段探索得出的經驗進行頭腦風暴一下:程序無限執行的能力,Linux沒有提供,但是這段Java程序能夠無限執行,說明這個能力是JVM賦予的。
那JVM如何做到的呢?首先,棧的內存大小決定了,一個程序的調用深度是有限的,超過了棧內存大小,Linux會觸發段錯誤信號:SIGSEGV。JVM應該是捕獲了這個信號,并進行了處理。那什么樣的處理能支持程序一直運行下去呢?如果你理解了剛剛那個棧圖你就清晰了,一定是做了棧幀回溯。
最終結論是:JVM捕獲了段錯誤信號并做了處理,處理方式是棧幀回溯。接下來我只貼核心代碼,有能力研究Hotspot源碼的可以去自行研究。當然,我的結論不一定就是百分百正確的,如果你有不一樣的結論并完成了論證,歡迎找我交流。
這里面還有個知識點我跳過了,我提一下,感興趣的自己去研究:yellow zone、red zone、glibc guard
JVM捕獲了異常,并為此創建了執行流
研究這個執行流一直往后面追,最終會追到這里
之前說的1024是怎么來的,就是MaxJavaStackTraceDepth的值。JVM默認支持的棧最大深度就是1024。為什么是1024,因為觸發異常的時候需要遍歷棧,導出棧信息,如果棧的深度很深,很費時間費性能,就取了一個有象征意義的值。
1024也是一個臨界點,當棧深度達到1024,棧幀開始回溯。你可以理解成程序跑起來把棧深度沖到1024,開始回溯,回溯到初始調用時的棧,然后棧深度又開始沖1024,循環往復
解釋執行時是這樣干的,那JIT編譯又做了哪些優化呢?
05
最終探索
針對回調做優化,目前主流的優化方式有:尾遞歸優化、內聯優化。JVM沒有用尾遞歸優化,而是用的內聯優化,專業名詞叫遞歸內聯。
此結論來自之前看的R大的某篇文章。本來想找到貼出來的,沒找著。有貼心的小伙伴找著了可以發給我,我貼出來。
到這里,這個問題就探索得無比清晰了。撒花~
作者:五角錢的程序員
原文鏈接:
https://mp.weixin.qq.com/s/BKq6dbkUbKtVEFhQDyK5ag