jvm之年輕代(新生代)、老年代、永久代以及GC原理詳解、GC優化


關於JVM,也許你聽過這些術語:年輕代(新生代)、老年代、永久代、minor gc(young gc)、major gc、full gc

不要急,先上圖,這是jvm 堆內存結構圖

 

 

仔細的你發現了 圖中有些分數8/10和1/10,這是默認配置下各個代內存分配比例。

舉個栗子:

假如總heap max分配1200M,那么年輕代占用1/3就是400M,老年代占2/3就是800M。

Eden占年輕代的8/10就是320M。Survivor占年輕代的2/10就是80M,from和to各占40M。

 

年輕代
也叫新生代,顧名思義,主要是用來存放新生的對象。新生代又細分為 Eden區、SurvivorFrom區、SurvivorTo區。

新創建的對象都會被分配到Eden區(如果該對象占用內存非常大,則直接分配到老年代區), 當Eden區內存不夠的時候就會觸發MinorGC(Survivor滿不會引發MinorGC,而是將對象移動到老年代中),

在Minor GC開始的時候,對象只會存在於Eden區和Survivor from區,Survivor to區是空的。

Minor GC操作后,Eden區如果仍然存活(判斷的標准是被引用了,通過GC root進行可達性判斷)的對象,將會被移到Survivor To區。而From區中,對象在Survivor區中每熬過一次Minor GC,年齡就會+1歲,當年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置,默認是15)的對象會被移動到年老代中,否則對象會被復制到“To”區。經過這次GC后,Eden區和From區已經被清空。

“From”區和“To”區互換角色,原Survivor To成為下一次GC時的Survivor From區, 總之,GC后,都會保證Survivor To區是空的。

奇怪為什么有 From和To,2塊區域?
這就要說到新生代Minor GC的算法了:復制算法

把內存區域分為兩塊,每次使用一塊,GC的時候把一塊中的內容移動到另一塊中,原始內存中的對象就可以被回收了,

優點是避免內存碎片。

老年代
隨着Minor GC的持續進行,老年代中對象也會持續增長,導致老年代的空間也會不夠用,最終會執行Major GC(MajorGC 的速度比 Minor GC 慢很多很多,據說10倍左右)。Major GC使用的算法是:標記清除(回收)算法或者標記壓縮算法。

    標記清除(回收):1. 首先會從GC root進行遍歷,把可達對象(存過的對象)打標記

                                    2. 再從GC root二次遍歷,將沒有被打上標記的對象清除掉。

        優點:老年代對象一般是比較穩定的,相比復制算法,不需要復制大量對象。之所以將所有對象掃描2次,看似比較消耗時間,其實不然,是節省了時間。舉個栗子,數組 1,2,3,4,5,6。刪除2,3,4,如果每次刪除一個數字,那么5,6要移動3次,如果刪除1次,那么5,6只需移動1次。

        缺點:這種方式需要中斷其他線程(STW),相比復制算法,可能產生內存碎片。

     標記壓縮:和標記清除算法基本相同,不同的就是,在清除完成之后,會把存活的對象向內存的一邊進行壓縮,這樣就可以解決內存碎片問題。 


當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)異常。

 

永久代(元空間)
 在Java8中,永久代已經被移除,被一個稱為“元數據區”(元空間,Metaspace)的區域所取代。


值得注意的是:元空間並不在虛擬機中,而是使用本地內存(之前,永久代是在jvm中)。這樣,解決了以前永久代的OOM問題,元數據和class對象存在永久代中,容易出現性能問題和內存溢出,畢竟是和老年代共享堆空間。java8后,永久代升級為元空間獨立后,也降低了老年代GC的復雜度。

 

 

Visual GC插件

自己不妨寫點代碼,測試下上面說過的GC過程,通過Visual GC插件

Java VisualVM安裝Visual GC插件

https://blog.csdn.net/yujianping_123/article/details/99549194

 

 

面說到了minor gc 和major gc,那么看下full gc

Full GC

 是清理整個堆空間—包括年輕代和老年代。

什么時候觸發:

1. 調用System.gc

2. 方法區空間不足

2.老年代空間不足,包括:

新創建的對象都會被分配到Eden區,如果該對象占用內存非常大,則直接分配到老年代區,此時老年代空間不足
做minor gc操作前,發現要移動的空間(Eden區、From區向To區復制時,To區的內存空間不足)比老年代剩余空間要大,則觸發full gc,而不是minor gc
等等
GC優化的本質,也是為什么分代的原因:減少GC次數和GC時間,避免全區掃描。

如何減少GC出現的次數(GC優化)

1.對象不用時最好顯式置為 Null
一般而言,為 Null 的對象都會被作為垃圾處理,所以將不用的對象顯式地設為 Null,有利於 GC 收集器判定垃圾,從而提高了 GC 的效率。

2.盡量少用 System.gc()
此函數建議JVM 進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主 GC,從而增加主 GC 的頻率,也即增加了間歇性停頓的次數。

3.盡量少用靜態變量
        靜態變量屬於全局變量,不會被 GC 回收,它們會一直占用內存。

4.盡量使用 StringBuffer, 而不用String 來累加字符串。
由於 String 是固定長的字符串對象,累加 String 對象時,並非在一個 String對象中擴增,而是重新創建新的 String 對象,如 Str5=Str1+Str2+Str3+Str4,這條語句執行過程中會產生多個垃圾對象,因為對次作“+”操作時都必須創建新的 String 對象,但這些過渡對象對系統來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用 StringBuffer 來累加字符串,因 StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。

5.分散對象創建或刪除的時間
集中在短時間內大量創建新對象,特別是大對象,會導致突然需要大量內存,JVM 在面臨這種情況時,只能進行主 GC,以回收內存或整合內存碎片,從而增加主 GC 的頻率。

集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閑空間必然減少,從而大大增加了下一次創建新對象時強制主 GC 的機會。

6.盡量少用 finalize 函數。
因為它會加大 GC 的工作量,因此盡量少用finalize 方式回收資源。

7.如果需要使用經常用到的圖片,可以使用軟引用類型,它可以盡可能將圖片保存在內存中,供程序調用,而不引起 OutOfMemory。

8.能用基本類型如 int,long,就不用 Integer,Long 對象
基本類型變量占用的內存資源比相應包裝類對象占用的少得多,如果沒有必要,最好使用基本變量。

9.增大-Xmx


免責聲明!

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



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