前置概念
- Java GC、新生代、老年代
Java 中的堆是 JVM 所管理的最大的一塊內(nèi)存空間,主要用于存放各種類的實(shí)例對象。
在 Java 中,堆被劃分成兩個不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。
新生代 ( Young ) 又被劃分為三個區(qū)域:Eden、From Survivor、To Survivor。
這樣劃分的目的是為了使 JVM 能夠更好的管理堆內(nèi)存中的對象,包括內(nèi)存的分配以及回收。 - 堆空間的分配
堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數(shù) –Xms、-Xmx 來指定。
默認(rèn)的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數(shù) –XX:NewRatio 來指定 ),
即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。
其中,新生代 ( Young ) 被細(xì)分為 Eden 和 兩個 Survivor 區(qū)域,這兩個 Survivor 區(qū)域分別被命名為 from 和 to,以示區(qū)分
默認(rèn)的,Edem : from : to = 8 :1 : 1 ( 可以通過參數(shù)–XX:SurvivorRatio 來設(shè)定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區(qū)域來為對象服務(wù),所以無論什么時(shí)候,總是有一塊Survivor區(qū)域是空閑著的。
因此,新生代實(shí)際可用的內(nèi)存空間為 9/10 ( 即90% )的新生代空間。
一、minor GC和full GC的區(qū)別
新生代GC(minor GC):指發(fā)生在新生代的垃圾收集動作,minor GC非常頻繁,回收速度也比較快。
新生代通常存活時(shí)間較短,因此基于復(fù)制算法來進(jìn)行回收,所謂復(fù)制算法就是掃描出存活的對象,并復(fù)制到一塊新的完全未使用的空間中.老生代GC(full GC/major GC):指發(fā)生在老生代的垃圾收集動作,出現(xiàn)了 Major GC 經(jīng)常會伴隨至少一次的 Minor GC(并非絕對),Major GC 的速度一般會比 Minor GC 的慢 10 倍以上。
舊生代與新生代不同,對象存活的時(shí)間比較長,比較穩(wěn)定,因此采用標(biāo)記(Mark)算法來進(jìn)行回收,所謂標(biāo)記就是掃描出存活的對象,然后再進(jìn)行回收未被標(biāo)記的對象,回收后對用空出的空間要么進(jìn)行合并,要么標(biāo)記出來便于下次進(jìn)行分配,總之就是要減少內(nèi)存碎片帶來的效率損耗
二、minorGC過程詳解
minor GC整體過程如下:
1,初始階段,新創(chuàng)建的對象被分配到Eden區(qū),survivor的兩塊空間都是空的。
2,當(dāng)Eden區(qū)滿了的時(shí)候,minor GC觸發(fā),經(jīng)過掃描與標(biāo)記,存活的對象被復(fù)制到S0,不存活的對象被回收, 并且存活的對象年齡都增大一歲。
3,在下一次的Minor GC中,Eden區(qū)的情況和上面一致,沒有引用的對象被回收,存活的對象被復(fù)制到survivor區(qū)。當(dāng)Eden 和 s0區(qū)空間滿了,S0的所有的數(shù)據(jù)都被復(fù)制到S1,需要注意的是,在上次minor GC過程中移動到S0中的兩個對象在復(fù)制到S1后其年齡要加1。此時(shí)Eden區(qū)S0區(qū)被清空,所有存活的數(shù)據(jù)都復(fù)制到了S1區(qū),并且S1區(qū)存在著年齡不一樣的對象。
4,再下一次MinorGC則重復(fù)這個過程,這一次survivor的兩個區(qū)對換,存活的對象被復(fù)制到S0,存活的對象年齡加1,Eden區(qū)和另一個survivor區(qū)被清空。
5,再經(jīng)過幾次Minor GC之后,當(dāng)存活對象的年齡達(dá)到一個閾值之后(-XX:MaxTenuringThreshold默認(rèn)是15),就會被從年輕代Promotion到老年代。
6, 隨著MinorGC一次又一次的進(jìn)行,不斷會有新的對象被promote到老年代。
7,上面基本上覆蓋了整個年輕代所有的回收過程。最終,MajorGC將會在老年代發(fā)生,老年代的空間將會被清除和壓縮(標(biāo)記-清除或者標(biāo)記整理)。
整體描述
大部分情況,對象都會首先在 Eden 區(qū)域分配,在一次新生代垃圾回收后,如果對象還存活,則會進(jìn)入 s1(“To”),并且對象的年齡還會加 1(Eden 區(qū)->Survivor 區(qū)后對象的初始年齡變?yōu)?1),當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置。經(jīng)過這次GC后,Eden區(qū)和"From"區(qū)已經(jīng)被清空。這個時(shí)候,“From"和"To"會交換他們的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To”。不管怎樣,都會保證名為To的Survivor區(qū)域是空的。Minor GC會一直重復(fù)這樣的過程,直到“To”區(qū)被填滿,"To"區(qū)被填滿之后,會將所有對象移動到年老代中。
總結(jié)
從上面的過程可以看出,Eden區(qū)是連續(xù)的空間,且Survivor總有一個為空。經(jīng)過一次GC和復(fù)制,一個Survivor中保存著當(dāng)前還活著的對象,而Eden區(qū)和另一個Survivor區(qū)的內(nèi)容都不再需要了,可以直接清空,到下一次GC時(shí),兩個Survivor的角色再互換。因此,這種方式分配內(nèi)存和清理內(nèi)存的效率都極高,這種垃圾回收的方式就是著名的“停止-復(fù)制(Stop-and-copy)”清理法(將Eden區(qū)和一個Survivor中仍然存活的對象拷貝到另一個Survivor中),這不代表著停止復(fù)制清理法很高效,其實(shí),它也只在這種情況下(基于大部分對象存活周期很短的事實(shí))高效,如果在老年代采用停止復(fù)制,則是非常不合適的。
老年代存儲的對象比年輕代多得多,而且不乏大對象,對老年代進(jìn)行內(nèi)存清理時(shí),如果使用停止-復(fù)制算法,則相當(dāng)?shù)托?。一般,老年代用的算法是?biāo)記-壓縮算法,即:標(biāo)記出仍然存活的對象(存在引用的),將所有存活的對象向一端移動,以保證內(nèi)存的連續(xù)。在發(fā)生Minor GC時(shí),虛擬機(jī)會檢查每次晉升進(jìn)入老年代的大小是否大于老年代的剩余空間大小,如果大于,則直接觸發(fā)一次Full GC,否則,就查看是否設(shè)置了-XX:+HandlePromotionFailure(允許擔(dān)保失?。?,如果允許,則只會進(jìn)行MinorGC,此時(shí)可以容忍內(nèi)存分配失??;如果不允許,則仍然進(jìn)行Full GC(這代表著如果設(shè)置-XX:+Handle PromotionFailure,則觸發(fā)MinorGC就會同時(shí)觸發(fā)Full GC,哪怕老年代還有很多內(nèi)存,所以,最好不要這樣做)。
三、GC觸發(fā)條件
Minor GC觸發(fā)條件:
- Eden區(qū)滿時(shí)
Full GC觸發(fā)條件:
- 調(diào)用System.gc時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
- 老年代空間不足
- 方法去空間不足
- 通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
- 由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時(shí),對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小。
四、對象進(jìn)入老年代的四種情況
- 假如進(jìn)行Minor GC時(shí)發(fā)現(xiàn),存活的對象在ToSpace區(qū)中存不下,那么把存活的對象存入老年代
- 大對象直接進(jìn)入老年代
假設(shè)新創(chuàng)建的對象很大,比如為5M(這個值可以通過PretenureSizeThreshold這個參數(shù)進(jìn)行設(shè)置,默認(rèn)3M),那么即使Eden區(qū)有足夠的空間來存放,也不會存放在Eden區(qū),而是直接存入老年代。 - 長期存活的對象將進(jìn)入老年代
此外,如果對象在Eden出生并且經(jīng)過1次Minor GC后仍然存活,并且能被To區(qū)容納,那么將被移動到To區(qū),并且把對象的年齡設(shè)置為1,對象沒"熬過"一次Minor GC(沒有被回收,也沒有因?yàn)門o區(qū)沒有空間而被移動到老年代中),年齡就增加一歲,當(dāng)它的年齡增加到一定程度(默認(rèn)15歲,配置參數(shù)-XX:MaxTenuringThreshold),就會被晉升到老年代中。 - 動態(tài)對象年齡判定
還有一種情況,如果在From空間中,相同年齡所有對象的大小總和大于Survivor空間的一半,那么年齡大于等于該年齡的對象就會被移動到老年代,而不用等到15歲(默認(rèn))。