天天看點

面試系列 | 一個線程OOM,程序裡其他線程還能運作麼?

這題是一個網友

@大臉貓愛吃魚

給我的提問,出自今年校招

美團三面

的一個真題。大緻如下:

一個程序有3個線程,如果一個線程抛出oom,其他兩個線程還能運作麼?

先說一下答案,答案是還

能運作

不瞞大家說,真在面試中,我遇到這一題,我估計也是答錯。因為我初看到這一題,内心嘿嘿一笑,覺得這題是在考察JVM的記憶體結構。我第一反應是OOM的常見情況堆溢出,也就是下面的這種異常:

java.lang.OutOfMemoryError: Java heap space
                

先回憶一下,多線程中棧與堆是公有的還是私有的?回答如下:

在多線程環境下,每個線程擁有一個棧和一個程式計數器。

棧和程式計數器用來儲存線程的執行曆史和線程的執行狀态,是線程私有的資源。其他的資源(比如堆、位址空間、全局變量)是由同一個程序内的多個線程共享。

也就是說,堆是線程共享。那麼一個線程堆抛出OOM異常,我第一反應是另外兩個線程也抛出OOM異常,畢竟堆是共有的,大家應該都抛出異常。于是,我機智的讓

@大臉貓愛吃魚

寫個代碼去測試一下,結果我被啪啪啪打臉了。

測試代碼僞如下:

一個線程去構造堆溢出,每隔1S申請一次堆,代碼長下面這樣:

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();
                

結果,輸出是長下面這樣的:

Fri Sep 21 11:18:39 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:39 CST 2018Thread[Thread-0,5,main]==
Fri Sep 21 11:18:40 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:40 CST 2018Thread[Thread-0,5,main]==
Fri Sep 21 11:18:41 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:41 CST 2018Thread[Thread-0,5,main]==
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at com.lpf.TestOOM.lambda$0(TestOOM.java:15)
	at com.lpf.TestOOM$$Lambda$1/531885035.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
Fri Sep 21 11:18:42 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:43 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:44 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:45 CST 2018Thread[Thread-1,5,main]==
Fri Sep 21 11:18:46 CST 2018Thread[Thread-1,5,main]==
                

大家發現了麼,一個線程溢出了,其他線程還在跑,這好像和我們的認知不大一樣。坦白說,我看到這個結果,瞬間覺得自己一世英名毀于一旦,從此無法擡起頭來做人。沒辦法了,隻能亮出工具來看一下了。

先說一下,在本例測試中,參數如下:

-Xms16m -Xmx32m
-Xms 初始堆記憶體 
-Xmx 最大堆記憶體
                

接下來,亮出

JvisualVM

看堆的變化,注意看上面那張圖,抛出OOM的時間約在00:11:45左右,是以我們需要重點關注00:11:45左右的曲線變化,如下圖所示:

面試系列 | 一個線程OOM,程式裡其他線程還能運作麼?

如圖所示,我們仔細觀察一下在14:06:20~14:06:21之間曲線變化,你會發現使用堆的數量,突然間急劇下滑!這代表這一點,當一個線程抛出OOM異常後,它所占據的記憶體資源會全部被釋放掉,進而不會影響其他線程的運作!

講到這裡大家應該懂了,此題的答案為

一個線程溢出後,程序裡的其他線程還能照常運作

。注意了,這個例子我隻示範了堆溢出的情況。如果是棧溢出,結論也是一樣的,大家可自行通過代碼測試。

完整代碼

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestOOM {

	public static void main(String[] args) {
		// 線程一
		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();

		// 線程二
		new Thread(() -> {
			while (true) {
				System.out.println(new Date().toString() + Thread.currentThread() + "==");
				try {
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
}
                

運作時設定記憶體參數

面試系列 | 一個線程OOM,程式裡其他線程還能運作麼?

繼續閱讀