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產生較大的影響。

在此我向大家推薦一個架構學習交流羣。交流學習羣號:697579751 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章