一個執行緒oom,程序裡其他執行緒還能執行嗎?
引言
這題是一個網友 @大臉貓愛吃魚
給我的提問,出自今年校招美團三面的一個真題。大致如下
一個程序有3個執行緒,如果一個執行緒丟擲oom,其他兩個執行緒還能執行麼?
先說一下答案,答案是 還能執行
不瞞大家說,真在面試中,我遇到這一題,我估計也是答錯。因為我初看到這一題,內心嘿嘿一笑,覺得這題是在考察JVM的記憶體結構。我第一反應是OOM的常見情況 堆溢位 ,也就是下面的這種異常
java.lang.OutOfMemoryError: Java heap space
先回憶一下,多執行緒中棧與堆是公有的還是私有的?回答如下
在多執行緒環境下,每個執行緒擁有一個棧和一個程式計數器。棧和程式計數器用來儲存執行緒的執行歷史和執行緒的執行狀態,是執行緒私有的資源。其他的資源(比如堆、地址空間、全域性變數)是由同一個程序內的多個執行緒共享。
也就是說,堆是執行緒共享。那麼一個執行緒堆丟擲OOM異常,我第一反應是另外兩個執行緒也丟擲OOM異常,畢竟堆是共有的,大家應該都丟擲異常。於是,我機智的讓 @大臉貓愛吃魚
寫個程式碼去測試一下,結果我被 啪啪啪 打臉了。
測試程式碼偽如下
一個執行緒去構造堆溢位,每隔1秒申請一次堆,程式碼長下面這樣
new Thread(() -> { List<byte[]> list=new ArrayList<byte[]>(); while(true){ System.out.println(new Date().toString()+Thread.currentThread()+"=="); byte[] b = new byte[1024*1024*1]; list.add(b); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }).start();
另一個執行緒,睡眠1秒然後輸出就好,程式碼長下面這樣
new Thread(() -> { while(true){ System.out.println(new Date().toString()+Thread.currentThread()+"=="); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }).start();
結果,輸出是長下面這樣的

大家發現了麼,一個執行緒溢位了,其他執行緒還在跑,這好像和我們的認知不大一樣。坦白說,我看到這個結果,瞬間覺得自己一世英名毀於一旦,從此無法抬起頭來做人。沒辦法了,只能亮出工具來看一下了。
先說一下,在本例測試中,引數如下
-Xms16m -Xmx32m -Xms 初始堆記憶體 -Xmx 最大堆記憶體
接下來,亮出 JvisualVM
看堆的變化,注意看上面那張圖,丟擲OOM的時間約在 00:11:45
左右,因此我們需要重點關注 00:11:45
左右的曲線變化,如下圖所示

如圖所示,我們仔細觀察一下在 00:11:44
~ 00:11:45
之間曲線變化,你會發現使用堆的數量,突然間急劇下滑!這代表著一點,當一個執行緒丟擲OOM異常後,它所佔據的記憶體資源會全部被釋放掉,從而不會影響其他執行緒的執行!
講到這裡大家應該懂了,此題的答案為 一個執行緒溢位後,程序裡的其他執行緒還能照常執行。 注意了,這個例子我只演示了堆溢位的情況。如果是棧溢位,結論也是一樣的,大家可自行通過程式碼測試。
說時遲,那時快。一個機智的網友又給我提了一個問題?
如果主執行緒拋異常退出了,子執行緒還能執行麼?
ok,這個問題要從子執行緒和主執行緒的關係講起。
先來一個定義
執行緒不像程序,一個程序中的執行緒之間是沒有父子之分的,都是平級關係。即執行緒都是一樣的, 退出了一個不會影響另外一個。
因此,答案是 如果主執行緒拋異常退出了,子執行緒還能執行。
但是有一個例外情況,如果這些子執行緒都是守護執行緒,那麼子執行緒會隨著主執行緒結束而結束。