1. 面試官:哪些場景會產(chǎn)生OOM?怎么解決?

        共 2993字,需瀏覽 6分鐘

         ·

        2020-09-28 20:18

        這個面試題是一個朋友在面試的時候碰到的,什么時候會拋出OutOfMemery異常呢?初看好像挺簡單的,其實深究起來考察的是對整個JVM的了解,而且這個問題從網(wǎng)上可以翻到一些亂七八糟的答案,其實在總結下來基本上4個場景可以概括下來。


        堆內(nèi)存溢出

        堆內(nèi)存溢出太常見,大部分人都應該能想得到這一點,堆內(nèi)存用來存儲對象實例,我們只要不停的創(chuàng)建對象,并且保證GC Roots和對象之間有可達路徑避免垃圾回收,那么在對象數(shù)量超過最大堆的大小限制后很快就能出現(xiàn)這個異常。

        寫一段代碼測試一下,設置堆內(nèi)存大小2M。


        public?class?HeapOOM?{
        ????public?static?void?main(String[]?args)?{
        ????????List?list?=?new?ArrayList<>();
        ????????while?(true)?{
        ????????????list.add(new?HeapOOM());
        ????????}
        ????}
        }

        運行代碼,很快能看見OOM異常出現(xiàn),這里的提示是Java heap space堆內(nèi)存溢出。


        一般的排查方式可以通過設置-XX: +HeapDumpOnOutOfMemoryError在發(fā)生異常時dump出當前的內(nèi)存轉(zhuǎn)儲快照來分析,分析可以使用Eclipse Memory Analyzer(MAT)來分析,獨立文件可以在官網(wǎng)下載。

        另外如果使用的是IDEA的話,可以使用商業(yè)版JProfiler或者開源版本的JVM-Profiler,此外IDEA2018版本之后內(nèi)置了分析工具,包括Flame Graph(火焰圖)和Call Tree(調(diào)用樹)功能。

        火焰圖


        方法區(qū)(運行時常量池)和元空間溢出

        方法區(qū)和堆一樣,是線程共享的區(qū)域,包含Class文件信息、運行時常量池、常量池,運行時常量池和常量池的主要區(qū)別是具備動態(tài)性,也就是不一定非要是在Class文件中的常量池中的內(nèi)容才能進入運行時常量池,運行期間也可以可以將新的常量放入池中,比如String的intern()方法。

        我們寫一段代碼驗證一下String.intern(),同時我們設置-XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m 元空間大小。由于我使用的是1.8版本的JDK,而1.8版本之前方法區(qū)存在于永久代(PermGen),1.8之后取消了永久代的概念,轉(zhuǎn)為元空間(Metaspace),如果是之前版本可以設置PermSize MaxPermSize永久代的大小。

        ?private?static?String?str?=?"test";
        ????public?static?void?main(String[]?args)?{
        ????????List?list?=?new?ArrayList<>();
        ????????while?(true){
        ????????????String?str2?=?str?+?str;
        ????????????str?=?str2;
        ????????????list.add(str.intern());
        ????????}

        }

        運行代碼,會發(fā)現(xiàn)代碼報錯。


        再次修改配置,去除元空間限制,修改堆內(nèi)存大小-Xms20m -Xmx20m,可以看見堆內(nèi)存報錯。


        這是為什么呢?intern()本身是一個native方法,它的作用是:如果字符串常量池中已經(jīng)包含一個等 于此String對象的字符串,則返回代表池中這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中,并且返回String對象的引用。

        而在1.7版本之后,字符串常量池已經(jīng)轉(zhuǎn)移到堆區(qū),所以會報出堆內(nèi)存溢出的錯誤,如果1.7之前版本的話會看見PermGen space的報錯。

        ?

        直接內(nèi)存溢出

        直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)域的一部分,并且不受堆內(nèi)存的限制,但是受到機器內(nèi)存大小的限制。常見的比如在NIO中可以使用native函數(shù)直接分配堆外內(nèi)存就容易導致OOM的問題。

        直接內(nèi)存大小可以通過-XX:MaxDirectMemorySize指定,如果不指定,則默認與Java 堆最大值-Xmx一樣。

        由直接內(nèi)存導致的內(nèi)存溢出,一個明顯的特征是在Dump文件中不會看見明顯的異常,如果發(fā)現(xiàn)OOM之后Dump文件很小,而程序中又直接或間接使用了NIO,那就可以考慮檢查一下是不是這方面的原因。

        ?

        棧內(nèi)存溢出

        棧是線程私有,它的生命周期和線程相同。每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,方法調(diào)用的過程就是棧幀入棧和出棧的過程。

        在java虛擬機規(guī)范中,對虛擬機棧定義了兩種異常:

        1. 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常

        2. 如果虛擬機棧可以動態(tài)擴展,并且擴展時無法申請到足夠的內(nèi)存,拋出OutOfMemoryError異常

        先寫一段代碼測試一下,設置-Xss160k,-Xss代表每個線程的棧內(nèi)存大小

        public?class?StackOOM?{
        ????private?int?length?=?1;

        ????public?void?stackTest()?{
        ????????System.out.println("stack?lenght="?+?length);
        ????????length++;
        ????????stackTest();
        ????}

        ????public?static?void?main(String[]?args)?{
        ????????StackOOM?test?=?new?StackOOM();
        ????????test.stackTest();
        ????}
        }

        測試發(fā)現(xiàn),單線程下無論怎么設置參數(shù)都是StackOverflow異常。


        嘗試把代碼修改為多線程,調(diào)整-Xss2m,因為為每個線程分配的內(nèi)存越大,棧空間可容納的線程數(shù)量越少,越容易產(chǎn)生內(nèi)存溢出。反之,如果內(nèi)存不夠的情況,可以調(diào)小該參數(shù)來達到支撐更多線程的目的。

        public?class?StackOOM?{
        ????private?void?dontStop()?{
        ????????while?(true)?{
        ????????}
        ????}

        ????public?void?stackLeakByThread()?{
        ????????while?(true)?{
        ????????????new?Thread(()?->?dontStop()).start();
        ????????}
        ????}

        ????public?static?void?main(String[]?args)?throws?Throwable?{
        ????????StackOOM?stackOOM?=?new?StackOOM();
        ????????stackOOM.stackLeakByThread();
        ????}
        }


        有道無術,術可成;有術無道,止于術

        歡迎大家關注Java之道公眾號


        好文章,我在看??

        瀏覽 56
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 国产在线资源 | 黃色A片中文字幕免费看 | 微信群加人 | 99热精品在线观看 | 日韩天堂在线 |