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