JVM 堆記憶體溢位後,其他執行緒是否可繼續工作
背景
最近網上出現一個美團面試題:“一個執行緒OOM後,其他執行緒還能執行嗎?”。我看網上出現了很多不靠譜的答案。這道題其實很有難度,涉及的知識點有jvm記憶體分配、作用域、gc等,不是簡單的是與否的問題。
由於題目中給出的OOM,java中OOM又分很多型別;比如:堆溢位(“java.lang.OutOfMemoryError: Java heap space”)、永久帶溢位(“java.lang.OutOfMemoryError:Permgen space”)、不能建立執行緒(“java.lang.OutOfMemoryError:Unable to create new native thread”)等很多種情況。
本文主要是分析 堆溢位對應用帶來的影響。
直接實驗驗證
日誌輸入:
從日誌可以看出在thead-0發生OOM之後,thread-1仍舊能夠繼續申請記憶體工作。使用jconsole監控發現,thread-0開始慢慢把heap壓滿,發生OOM之後神奇的事情發生了,heap基本上被清空了,通過檢視 jconsole看到的執行緒資訊,發現沒有thead-0執行緒了。這就很明確了,因為thead-0沒有捕獲該異常,跳出了while迴圈,導致thead-0執行緒執行結束,該執行緒持有的物件也就能被釋放了。
那如果thread-0發生了OOM,但是該執行緒仍舊存活並且持有這些物件會怎麼樣呢?
線上程thread-0我們捕獲了該ERROR,然後讓該執行緒暫停(不要讓他結束,不然又像上面那樣了)輸出日誌如下:
在thread-0發生OOM之後,thread-1在申請記憶體也就發生了OOM,這個很容易理解的。
原理分析
我們知道java物件基本上都是在堆上分配(有特殊情況下,不在我們討論的範圍內)。小物件都是直接在Eden區域中分配。如果此時記憶體不夠,就會發生young gc,如果釋放之後還是記憶體不夠,此時jvm會進行full gc。如果發生full gc之後記憶體還是不夠,此時就會丟擲“java.lang.OutOfMemoryError: Java heap space”。大物件jvm會直接在old 區域中申請,但是和小物件分配的原理類似。
一般情況下,java物件記憶體分配跟執行緒無關(TLAB例外),能夠申請成功至於當前只和當前heap空餘空間有關。
清楚了記憶體分配原理之後,我們就可以以此為基礎來分析各種情況。比如:在MyThread0中bytesList放在try中,程式碼如下:
MyThread0發生OOM之後,bytesList其實就不屬於存活物件,gc的時候就被釋放了。
再比如發生OOM捕獲該異常之後,因為日誌輸入的string需要佔用heap空間,也可能導致 MyThread0再次發生OOM, MyThread0執行緒終結。
再比如 MyThread0中一次性申請的記憶體太大,比如超過heap大小;其他申請小記憶體的執行緒肯定不會受到影響。
總結
發生OOM之後會不會影響其他執行緒正常工作需要具體的場景分析。但是就一般情況下,發生OOM的執行緒都會終結(除非程式碼寫的太爛),該執行緒持有的物件佔用的heap都會被gc了,釋放記憶體。
因為發生OOM之前要進行gc,就算其他執行緒能夠正常工作,也會因為頻繁gc產生較大的影響。