Java8內存survival區域

java OOM內存異常的四種類型及異常與解決方案

       OOM異常的四種類型:

   

  一: StackOverflowError :通常因爲遞歸函數引起(死遞歸,遞歸太深)。-Xss 128k 一般夠用。

 

     二: out Of memory: PermGen Space:通常是動態類大多,比如web 服務器自動更新部署時引起。-Xmx 256M,一般夠用。JDK 8 沒有PermGen Space,相對應是MetaSpace

 

       三:  OutOfMemoryError:unable to create native thread : 線程數太多(查看下線程數)或者給虛擬機內存過大( -Xmx 值小點)

 

      四:   out Of memory :heap space  : 沒有及時釋放對象,主要查下各類集合引用的對象。這類問題最難查,可借且 jvisualvm 程序,仔細分析

      

       下面,開始講道理

       遇到內存問題,大多數程序員會覺得頭痛,看到報內存異常問題不知怎麼辦,覺得太多專業術語。所以,我打算用最用最簡潔的話,解釋清楚內存問題。

       從開頭講起,計算機在運行程序的時候,都是一條條指令的運行,那麼運行到哪裏了,總得有個標記,這個標記就是放在程序計數寄存器中。

      不管我們執行的是進程還是線程,最終要執行的都是一個個的方法(函數 function)。方法有傳入的參數,還有傳出的參數,執行過程中,還有變量,那麼總得給這個方法一塊內存,我們叫這塊內存爲棧(stack),這塊內存在方法執行完後,就沒必要存在了,就會清理掉,所以你也可以理解成“暫” 時的。

      方法運行的時候,會生成些結果,有些結果佔用的內存比較大,不適合用參數來傳遞,而且這個結果可能要保留很久,所以需要一個大塊的內存,叫“堆”(heap),可以想象成一個大倉庫,總是要堆些東西。在 c 裏面用 alloc() 分配,free()還釋放, java裏用 new 來分配。

      在java 中,還有個叫本地棧,專門給調用native 程序用的,比如win32的代碼。

     

      好了,基本概念就如上,很簡單。下面講內存釋放問題。

      計算機的內存總是有限的,所以,使用完一塊內存,最好馬上釋放掉,給其它程序用。使用很簡單,new 一下,一塊內存就被用了。但釋放就沒那麼簡單了,因爲不知道誰該來釋放。打個比方,就象超市的塑料袋,廠家生產出來後,給超市,超市又給顧客,那顧客用完後是不是會妥善的把塑料袋處理掉?扔在陰溝裏的塑料袋得怎麼處理?

      對C/C++程序,必須仔細規劃程序結構,及時的調用free()。不過,現實情況就象塑料袋一樣,free()函數有時候還真不知道放哪裏最合適,即保證需要的人還能用,又要確保沒有人用的時候要銷燬。

     

       所以java就想到自動回收內存機制

       回收的原理就象有輛垃圾車,整天都在城市裏轉,發現垃圾就收回來。

       首先,垃圾車得發現垃圾,基本的就是 計數法,如果某個對象 A,被B使用了,計數值就加1,不被使用了就減1。如果計數值是0,那麼就回收。這個叫 計數/清理法  。 

       計數法會出現問題是,如果A引用B,B引用A,但A和B都不被主程序引用,那麼A和B應該都被清理,但計數值又不是0,所以無法清理,針對這個問題,就產生了標記法。標記法從主程序的對象(root obecjt)開始找,如果某個對象(比如A)被主程序引用,那就是活的,該對象又引用其它的(比如B),那麼B也是活的。 如果C對象不被A和B引用,那麼C就是死的。i活與死都做個標記,這就叫標記法 

       發現之後,就得清理,怎麼清理呢?最簡單的就是把內存標記爲未用。這叫清理法

       簡單的清理,會引起內存碎片,比如家裏的冰箱,一個個格子裏都塞些小零碎,現在要放整個西瓜進去,怎麼辦? 那就要整理,把小塊的碎片集中放,騰出地方放西瓜。這叫整理法。

       如果整理東西的時候,地方有限,那小塊的碎片得左騰右挪的,效率比較低,所以,最好是有塊空的場地,和原來一樣大,那整理起來順手多了,所以就搞兩塊內存,一塊內存是當前使用的,另一塊內存是整理的時候用的,整理的時候把當前用的複製過去,這叫做 複製法。 

        簡單的複製法有個問題,內存有一半平時不用,比較浪費。經過研究發現,有大量對象生命期很短,也就是說要複製的內存其實不大。所以java 把當前使用的分配得大些,叫eden區,第一次複製的時候把eden複製到一個小塊的區域,叫 survival 區。再下一次的時候,檢查eden區和survival區還活着的對象,再複製到第二個survival區。所以,survial區有兩塊。

       這樣做了後,還有個問題,有些對象要活很久,總是在 survival區複製來複制去,浪費時間,所以複製到一定次數後,被判斷爲長期有效對象,於是就另外再開闢一塊內存,叫它老年代內存,把老年對象複製到這裏去。而eden和survival叫新生代區。年老代的內存也會被清理,只是不象新生代區那麼頻繁,而且不會採用複製法,只採用整理法,因爲沒多餘的內存。

        另外,java是個動態語言,運行時可以拿到類的定義信息,還可以產生動態的類,所以需要一個區域專門放類的定義 ,在JDK 8之前 叫永久代區 permanent generation ,永久代區的信息很難清理,因爲不知道某個類是不是會再次被用到。而且最初sun 也只是設置了較小的區域給永久代,因爲認爲類在程序啓動的時候都基本被確定了,不需要很大,但是因爲現在的程序大量使用到動態特性,比如web程序中的jsp,就是會被動態編譯,各種框架也會用到動態代理機制,所以產生了大量類放到了永久代,不小心就產生了PermGen out of memeory 錯誤。到了JDK 8,爲了解決這個問題,取而代之的是 MetaSpace,其實MetaSpace只是默認下放大的限制,默認是無限。。。

 

       講到這裏,垃圾回收的發現與清理叫完了,接下來得講怎麼執行。也就是垃圾車多久去逛下城市,是隻派一輛車嗎?還是多派幾輛。如果只派一輛,那就是串行執行,如果多派幾輛,那麼就是並行執行。

       針對 新生代和年老代,是有不同的執行者,採用的策略也有點不一樣。

       

       下面再給JVM 的參數

垃圾蒐集器選擇參數

 

           UseSerialGC:開啓此參數使用serial & serial old蒐集器(client模式默認值)。

           UseParNewGC:開啓此參數使用ParNew & serial old蒐集器(不推薦)。

           UseConcMarkSweepGC:開啓此參數使用ParNew & CMS(serial old爲替補)蒐集器。

           UseParallelGC:開啓此參數使用parallel scavenge & parallel old蒐集器(server模式默認值)。

           UseParallelOldGC:開啓此參數在年老代使用parallel old蒐集器(該參數在JDK1.5之後已無用)。

 

JVM各個內存區域大小相關參數

 

           Xms:堆的初始值。默認爲物理內存的1/64,最大不超1G。

           Xmx:堆的最大值。默認爲物理內存的1/4,最大不超1G。

           Xmn:新生代的大小。

           Xss:線程棧大小。

           PermSize:永久代初始大小。默認爲物理內存的1/64,最大不超1G。

           MaxPermSize:永久代最大值。默認爲物理內存的1/4,最大不超1G。

           NewRatio:新生代與年老代的比例。比如爲3,則新生代佔堆的1/4,年老代佔3/4。

           SurvivorRatio:新生代中調整eden區與survivor區的比例,默認爲8,即eden區爲80%的大小,兩個survivor分別爲10%的大小。(備註:這個參數設定是講解複製算法那一章中,解決複製算法內存減半的辦法。eden區即是複製算法一章中80%的那部分,而survivor區則是兩個10%的那部分。)

 

垃圾蒐集器性能 通用參數

          

           PretenureSizeThreshold:晉升年老代的對象大小。默認爲0,比如設爲10M,則超過10M的對象將不在eden區分配,而直接進入年老代。

           MaxTenuringThreshold:晉升老年代的最大年齡。默認爲15,比如設爲10,則對象在10次普通GC後將會被放入年老代。

           DisableExplicitGC:禁用System.gc()。

 

並行蒐集器參數

 

           ParallelGCThreads:回收時開啓的線程數。默認與CPU個數相等。

           GCTimeRatio:設置系統的吞吐量。比如設爲99,則GC時間比爲1/1+99=1%,也就是要求吞吐量爲99%。若無法滿足會縮小新生代大小。

           MaxGCPauseMillis:設置垃圾回收的最大停頓時間。若無法滿足設置值,則會優先縮小新生代大小,仍無法滿足的話則會犧牲吞吐量。

          

併發蒐集器參數

 

           CMSInitiatingOccupancyFraction:觸發CMS收集器的內存比例。比如60%的意思就是說,當內存達到60%,就會開始進行CMS併發收集。

           UseCMSCompactAtFullCollection:這個前面已經提過,用於在每一次CMS收集器清理垃圾後送一次內存整理。

 

           CMSFullGCsBeforeCompaction:設置在幾次CMS垃圾收集後,觸發一次內存整理。

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