jvm堆內存年輕代觸發MInorGC和和老年代觸發FullGC的場景分析


 

了解什么是內存碎片?

大量的實例對象在堆內存新生代中因為沒有了棧內存的局部變量的引用,而成為新生代中需要被垃圾回收的垃圾對象.此時垃圾對象被回收之后,垃圾對象所占用的內存區域就成為了內存碎片.

了解什么是新生代的復制算法?

因為內存碎片的原因,可能導致大量的垃圾對象雖然被回收了.但是內存區域都是一小塊一小塊的,並不能被新產生的實例對象所使用.從而產生了內存浪費因此需要一種方法,能夠將內存中存活的對象給移動到另一個塊兒內存區域中,並且緊密排列,然后對剩下的批量對垃圾對象進行統一清除.然后新產生的對象就繼續放到新的內存區域內,等到新內存區域同樣滿了的時候,就將該區域內的存活對象移動到上一塊兒被清除過的內存區域中,然后全量回收垃圾對象. 兩塊兒內存區域各一半新生代內存.這就是復制算法.

復制算法缺點:

上面的描述,解決了內存碎皮的問題,但是產生了一個新的問題,就是兩塊兒區域來回倒騰的過程中,始終有一塊兒內存區域被閑置.這樣就同樣會產生內存浪費.

復制算法優化:

采用三塊兒內存區域,並且設置內存區域的比例為8:1:1,然后三者進行來回切換.   比例為8的叫Eden區(伊甸園區),比例為1的統稱為Survivor區(存活區), 剛開始分配對象的時候以Eden區為主,等到快滿的時候,將存活對象移動到Survivor1區,然后對Eden區進行MinorGC,然后下次實例化的對象將會繼續放在Eden區,等到下次Eden區滿的時候,就會將Eden區的對象和上次放在Survivor的存活對象批量移動到另一個Survivor2區,然后再對Eden區和Survivor1區進行垃圾對象的回收清空.以此循環往復,就能保證始終只有十分之一的內存區域被閑置,而十分之九的內存可以被使用.

 

為什么要設置新生代對象的年齡?

所以從一開始就可以設置新生代對象的年齡.通過-XX:MaxTenuringTreshold參數設置的低一點,默認值為15,比如這里我們設置為5.

目的就是讓其防止一次性堆積太多對象.


什么時候新生代的存活對象會到老年代中?

上面的復制算法雖然好,但是總會產生存活對象滿了的情況,這個時候大對象想要放入新生代放不下區,該怎么辦?

第一種的情況就是將躲過15次MinorGC的對象移動到老年代.

第二種就是動態年齡對象判斷,既Survivor區的經歷過兩次GC的對象大小大於Survivor區容量的一半的時候,如Survivor區是100m,里面的對象之和大於50m.就將這些2次GC還存活的對象移入到老年代中去.

第三種就是大對象,還沒有進入到新生代的時候就被移動到老年代,這里有個參數,為-XX:PretenureSizeThreshold,可以設置值,比如1m,那么再進入堆內存的時候,就會檢查這個實例對象的大小,如果大於這個閾值,就直接進入到老年代.

 

如果Eden區中對象過多,加上Survivor1區的存活對象,總容量大於了容量為堆內存的容量10分之一的Survivor2區.這個時候該怎么辦呢?

此時會通過一個參數-XX:-HandlePromotionFalure是否允許擔保失敗來進行判斷(jdk1.6以前可以用,之后的版本被廢棄)為true,則會判斷老年帶可用內存空間是否大於歷次進行移動老年代的對象容量的平均值,

如果大於,則嘗試進行一次Minor GC(Yong GC),但這次Minor GC(Yong GC)依然是有風險的,失敗后會重新發起一次Major GC(Full GC);
如果小於或者HandlePromotionFailure=false,則改為直接進行一次Major GC(Full GC)老年代回收.盡量騰出一點空間

(但是在jdk1.6 update 24之后-XX:-HandlePromotionFailure 不起作用了,只要老年代的連續空間大於新生代對象的總大小或者歷次晉升到老年代的對象的平均大小就進行MinorGC,否則FullGC)

fullGC完畢之后,再嘗試進行以此MinorGC.   這里會有三個問題,第一,GC完了之后的存活對象剛好能夠放入到Survivor2區中.

那么就可以不用進入老年代了.

第二就是GC完了存活對象大於Survivor2區的容量,並且小於老年代的可用容量.

第三就是MinorGC完了后存活對象大於Survivor2區的容量,並且也大於老年代的可用容量,此時就會再次對老年代進行fullGC.如果GC完了還是不夠存放那些存活對象.就會出現我們常見的OOM異常.內存溢出了.

 

老年代的標記整理算法是什么?

新生代類似的問題,回收過程中為了避免內存碎片的產生.這里老年代並沒有給自己分配空白內存區域.而是采用了標記整理算法.

本來是雜亂無章的存放在老年帶內存中,現在將這些存活的對象進行標記,並且不斷的移動位置.統一移動到相對緊湊的內存區域.留出大量的空間和垃圾對象,然后一次性對剩下的垃圾對象進行回收處理.

 

為什么要減少老年代的FullGC操作?

老年代的FullGC是十分緩慢的.其標記整理算法至少比新生代的復制算法要慢十倍.所以應該盡量減少老年代的垃圾回收.有種場景就是新生代的Eden和兩個survivor區的大小分別是800兆,100m,100m.這樣當每次eden區存活對象為200m的時候是進不去survivor區的.只能進入老年代中去,這樣就會使老年代沒過多久就會被填滿.就會出問題.老年代的FullGC過於緩慢.為了避免存活對象快速的進入老年代去.這里應該讓存活對象能夠survivor區和Eden區快速流轉..所以新生代要設置大一點.比如設置surivor區的大小為200m.也就是對應的堆內存新生代為2000m.eden區為1600m.這就是為什么一半新生代的大小設置的要比老年代多的原因.

上述方案雖然能解決一部分問題,但是因為動態年齡對象判斷,既Survivor區的經歷過兩次GC的對象大小大於Survivor區容量的一半的時候,如Survivor區是100m,里面的對象之和大於50m.就將這些2次GC還存活的對象移入到老年代中去.   這個時候仍然可能存在對象頻繁進入老年代去的.這個時候我們可以通過一個參數XX:SurvivorRatio=8設置eden區占新生代的比例.讓survivor區的大小盡量大一點.就可以避免動態年齡對象判斷所產生的對象進入老年代的問題.

 

 

什么是Stop the World?

系統在進行垃圾回收的時候會暫停java程序的執行.直到垃圾回收完成.所謂jvm的迭代演進其實就是基本上就是在解決減少垃圾回收,和垃圾回收的時間盡可能的短.一起都取決於垃圾回收算法.

新生代的垃圾回收器ParNew是什么?

參數為-XX:+UserParNewGC    默認值為CPU核數,該新生代垃圾回收器針對多核CPU做到了資源利用,可以支持多個線程進行執行垃圾回收.提升垃圾回收的性能,減少垃圾回收的時間

另外如果想要針對多核cpu做配置,-XX:ParallelGCThreads可以設置垃圾回收器的線程數,但是該值一般不用設置,系統默認就是幾核cpu就幾個線程.

平時進行啟動java程序的時候有跟着加-server的,還有跟着加-client的,這是啥意思呢?

其實只要理解為java寫的一些服務器程序,比如只是提供接口的程序,並且運行再linux的程序,可以設置-server,因為可以充分利用linux多核的cpu.此時可以提高運行效率.   相反,如果程序運行在windows的程序比如百度網盤,此時可以加上-client.證明該程序是客戶端程序

服務器程序通常是網站系統,電商系統等大型系統,擁有很好的CPU支持,所以垃圾回收時可以采用ParNew充分利用其多線程特性.加快垃圾回收速度.   如果用了單線程垃圾回收,則會存在資源浪費.

客戶端程序通常一個單核CPU,如果還是用ParNew開辟多個線程,反而加重了程序運行的負擔.所以windows系統建議采用Serial垃圾回收器,單線程回收即可.但是很少現在客戶端有用java寫的.所以-client不常用.

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM