GC 垃圾收集 Garbage Collection
GC主要做了清理對(duì)象,整理內(nèi)存的工作。Java堆分為新生代和老年代炼邀,采用了不同的回收方式。
Java中一個(gè)接口的多個(gè)實(shí)現(xiàn)類所需要的內(nèi)存可能不一樣剪侮,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣拭宁,我們只有在程序處于運(yùn)行期間時(shí)才會(huì)知道創(chuàng)建了哪些對(duì)象,這部分內(nèi)存的分配時(shí)動(dòng)態(tài)的瓣俯,而程序計(jì)數(shù)器杰标、虛擬機(jī)棧、本地方法棧這幾個(gè)區(qū)域內(nèi)存的分配和回收都是確定的彩匕,因此垃圾收集器關(guān)注的就是Java堆腔剂。
正式閱讀之前需要了解相關(guān)概念:
JVM區(qū)域總體分兩類,heap區(qū)和非heap區(qū)驼仪。
heap區(qū)又分為:
Eden Space(伊甸園)掸犬、
Survivor Space(幸存者區(qū))、
Old Gen(老年代)绪爸。
非heap區(qū)又分:
Code Cache(代碼緩存區(qū))湾碎;
Perm Gen(永久代);
Jvm Stack(java虛擬機(jī)棧)奠货;
Local Method Statck(本地方法棧)介褥;
Java 堆內(nèi)存分為新生代和老年代,新生代中又分為1個(gè) Eden(對(duì)象被創(chuàng)建的時(shí)候首先放到這個(gè)區(qū)域递惋,進(jìn)行垃圾回收后柔滔,不能被回收的對(duì)象被放入到空的survivor區(qū)域。) 區(qū)域 和 2個(gè) Survivor(用于保存在eden space內(nèi)存區(qū)域中經(jīng)過垃圾回收后沒有被回收的對(duì)象) 區(qū)域丹墨。
- 年輕代(Young Gen):
Eden Space字面意思是伊甸園廊遍,對(duì)象被創(chuàng)建的時(shí)候首先放到這個(gè)區(qū)域,進(jìn)行垃圾回收后贩挣,不能被回收的對(duì)象被放入到空的survivor區(qū)域喉前。
Survivor Space幸存者區(qū),用于保存在eden space內(nèi)存區(qū)域中經(jīng)過垃圾回收后沒有被回收的對(duì)象王财。Survivor有兩個(gè)卵迂,分別為To Survivor、 From Survivor绒净,這個(gè)兩個(gè)區(qū)域的空間大小是一樣的见咒。執(zhí)行垃圾回收的時(shí)候Eden區(qū)域不能被回收的對(duì)象被放入到空的survivor(也就是To Survivor,同時(shí)Eden區(qū)域的內(nèi)存會(huì)在垃圾回收的過程中全部釋放)挂疆,另一個(gè)survivor(即From Survivor)里不能被回收的對(duì)象也會(huì)被放入這個(gè)survivor(即To Survivor)改览,然后To Survivor 和 From Survivor的標(biāo)記會(huì)互換下翎,始終保證一個(gè)survivor是空的。
有關(guān)年輕代的JVM參數(shù)
用于設(shè)置年輕代的大小宝当,建議設(shè)為整個(gè)堆大小的1/3或者1/4,兩個(gè)值設(shè)為一樣大视事。
-XX:NewSize和-XX:MaxNewSize
用于設(shè)置Eden和其中一個(gè)Survivor的比值,這個(gè)值也比較重要庆揩。
-XX:SurvivorRatio
這個(gè)參數(shù)用于顯示每次Minor GC時(shí)Survivor區(qū)中各個(gè)年齡段的對(duì)象的大小俐东。
-XX:+PrintTenuringDistribution
用于設(shè)置晉升到老年代的對(duì)象年齡的最小值和最大值,每個(gè)對(duì)象在堅(jiān)持過一次Minor GC之后订晌,年齡就加1虏辫。
-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
- 年老代(Tenured Gen):
年老代主要存放JVM認(rèn)為生命周期比較長(zhǎng)的對(duì)象(經(jīng)過幾次的Young Gen的垃圾回收后仍然存在),內(nèi)存大小相對(duì)會(huì)比較大锈拨,垃圾回收也相對(duì)沒有那么頻繁(譬如可能幾個(gè)小時(shí)一次)砌庄。年老代主要采用壓縮的方式來避免內(nèi)存碎片(將存活對(duì)象移動(dòng)到內(nèi)存片的一邊,也就是內(nèi)存整理)推励。當(dāng)然鹤耍,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能會(huì)不進(jìn)行壓縮验辞。 - 持久代(Perm Gen):
Perm Gen全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域喊衫,因而稱之為永久代跌造。這個(gè)內(nèi)存區(qū)域用于存放Class和Meta的信息,Class在被 Load的時(shí)候被放入這個(gè)區(qū)域族购。因?yàn)镻erm里存儲(chǔ)的東西永遠(yuǎn)不會(huì)被JVM垃圾回收的壳贪,所以如果你的應(yīng)用程序LOAD很多CLASS的話,就很可能出現(xiàn)PermGen space錯(cuò)誤寝杖。默認(rèn)大小為物理內(nèi)存的1/64违施。 - Code Cache代碼緩存區(qū),它主要用于存放JIT所編譯的代碼瑟幕。CodeCache代碼緩沖區(qū)的大小在client模式下默認(rèn)最大是32m磕蒲,在server模式下默認(rèn)是48m,這個(gè)值也是可以設(shè)置的只盹,它所對(duì)應(yīng)的JVM參數(shù)為ReservedCodeCacheSize 和 InitialCodeCacheSize辣往,可以通過如下的方式來為Java程序設(shè)置。
-XX:ReservedCodeCacheSize=128m
CodeCache緩存區(qū)是可能被充滿的殖卑,當(dāng)CodeCache滿時(shí)站削,后臺(tái)會(huì)收到CodeCache is full的警告信息,如下所示:
“CompilerThread0” java.lang.OutOfMemoryError: requested 2854248 bytes for Chunk::new. Out of swap space?
注:JIT編譯器是在程序運(yùn)行期間孵稽,將Java字節(jié)碼編譯成平臺(tái)相關(guān)的二進(jìn)制代碼许起。正因?yàn)榇司幾g行為發(fā)生在程序運(yùn)行期間十偶,所以該編譯器被稱為Just-In-Time編譯器。
需要GC的內(nèi)存區(qū)域
jvm 中园细,程序計(jì)數(shù)器惦积、虛擬機(jī)棧、本地方法棧都是隨線程而生隨線程而滅珊肃,棧幀隨著方法的進(jìn)入和退出做入棧和出棧操作荣刑,實(shí)現(xiàn)了自動(dòng)的內(nèi)存清理,因此伦乔,我們的內(nèi)存垃圾回收主要集中于 java 堆和方法區(qū)中厉亏,在程序運(yùn)行期間,這部分內(nèi)存的分配和使用都是動(dòng)態(tài)的烈和。
判斷對(duì)象是否存活
需要進(jìn)行回收的對(duì)象就是已經(jīng)沒有存活的對(duì)象爱只,判斷一個(gè)對(duì)象是否存活常用的有兩種辦法:引用計(jì)數(shù)和可達(dá)分析。
(1)引用計(jì)數(shù):每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)屬性招刹,新增一個(gè)引用時(shí)計(jì)數(shù)加1恬试,引用釋放時(shí)計(jì)數(shù)減1,計(jì)數(shù)為0時(shí)可以回收疯暑。此方法簡(jiǎn)單训柴,無法解決對(duì)象相互循環(huán)引用的問題。
A a=new A();
B b=new B();
a.object=B;
b.object=A;
a=null;
b=null;
實(shí)際上這兩個(gè)對(duì)象都不可能再被訪問妇拯,但是因?yàn)樗鼈兓ハ嘁脤?dǎo)致引用計(jì)數(shù)不為0無法被回收幻馁。為了解決這個(gè)問題,Java采用下面的可達(dá)性分析算法越锈。
(2)可達(dá)性分析(Reachability Analysis):從GC Roots開始向下搜索仗嗦,搜索所走過的路徑稱為引用鏈。當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)甘凭,則證明此對(duì)象是不可用的稀拐。不可達(dá)對(duì)象。
在Java語言中丹弱,GC Roots包括:
虛擬機(jī)棧中引用的對(duì)象德撬。
方法區(qū)中類靜態(tài)屬性實(shí)體引用的對(duì)象。
方法區(qū)中常量引用的對(duì)象蹈矮。
本地方法棧中JNI引用的對(duì)象砰逻。
引用
如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個(gè)引用泛鸟。
這個(gè)定義過于簡(jiǎn)單以至于沒有實(shí)用價(jià)值蝠咆。 JDK1.2之后對(duì)引用的概念進(jìn)行了擴(kuò)充。
1.強(qiáng)引用:在程序代碼中普遍存在的類似“Object obj = new Object()”這類的引用,只要強(qiáng)引用還在刚操,垃圾回收器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象闸翅。
2.軟引用 :描述還有用但并非必須的對(duì)象。僅在系統(tǒng)內(nèi)存不足時(shí)進(jìn)行清理菊霜。對(duì)應(yīng)的類是SoftReference
3.弱引用:描述非必需對(duì)象坚冀。無論內(nèi)存是否足夠都會(huì)被清理。對(duì)應(yīng)的類時(shí)WeakReference
4.虛引用:最弱的引用關(guān)系鉴逞,一個(gè)對(duì)象是否有虛引用的存在完全不會(huì)對(duì)其生存時(shí)間造成影響记某,也無法通過虛引用來取得一個(gè)對(duì)象實(shí)例,為對(duì)象設(shè)置虛引用的唯一目的是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知构捡。對(duì)應(yīng)的類是PhantomReference
引用強(qiáng)度:強(qiáng)引用》軟引用》弱引用》虛引用
什么時(shí)候觸發(fā)GC
(1)程序調(diào)用System.gc時(shí)可以觸發(fā)
(2)系統(tǒng)自身來決定GC觸發(fā)的時(shí)機(jī)(根據(jù)Eden區(qū)和From Space區(qū)的內(nèi)存大小來決定液南。當(dāng)內(nèi)存大小不足時(shí),則會(huì)啟動(dòng)GC線程并停止應(yīng)用線程)
GC又分為 minor GC 和 Full GC (也稱為 Major GC )
Minor GC觸發(fā)條件:當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC。
Full GC觸發(fā)條件:
a.調(diào)用System.gc時(shí)吨凑,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
b.老年代空間不足
c.方法去空間不足
d.通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
e.由Eden區(qū)畅姊、From Space區(qū)向To Space區(qū)復(fù)制時(shí),對(duì)象大小大于To Space可用內(nèi)存吹由,則把該對(duì)象轉(zhuǎn)存到老年代若未,且老年代的可用內(nèi)存小于該對(duì)象大小
GC常用算法
GC常用算法有:標(biāo)記-清除算法,標(biāo)記-壓縮算法倾鲫,復(fù)制算法陨瘩,分代收集算法。
目前主流的JVM(HotSpot)采用的是分代收集算法级乍。
標(biāo)記-清除算法
為每個(gè)對(duì)象存儲(chǔ)一個(gè)標(biāo)記位,記錄對(duì)象的狀態(tài)(活著或是死亡)帚湘。分為兩個(gè)階段玫荣,一個(gè)是標(biāo)記階段,這個(gè)階段內(nèi)大诸,為每個(gè)對(duì)象更新標(biāo)記位捅厂,檢查對(duì)象是否死亡;第二個(gè)階段是清除階段资柔,該階段對(duì)死亡的對(duì)象進(jìn)行清除焙贷,執(zhí)行 GC 操作。
優(yōu)點(diǎn)
最大的優(yōu)點(diǎn)是贿堰,標(biāo)記—清除算法中每個(gè)活著的對(duì)象的引用只需要找到一個(gè)即可辙芍,找到一個(gè)就可以判斷它為活的。此外,更重要的是故硅,這個(gè)算法并不移動(dòng)對(duì)象的位置庶灿。
缺點(diǎn)
它的缺點(diǎn)就是效率比較低(遞歸與全堆對(duì)象遍歷)。每個(gè)活著的對(duì)象都要在標(biāo)記階段遍歷一遍吃衅;所有對(duì)象都要在清除階段掃描一遍往踢,因此算法復(fù)雜度較高。沒有移動(dòng)對(duì)象徘层,導(dǎo)致可能出現(xiàn)很多碎片空間無法利用的情況峻呕。
標(biāo)記-壓縮算法(標(biāo)記-整理)
標(biāo)記-壓縮法是標(biāo)記-清除法的一個(gè)改進(jìn)版。同樣趣效,在標(biāo)記階段瘦癌,該算法也將所有對(duì)象標(biāo)記為存活和死亡兩種狀態(tài);不同的是英支,在第二個(gè)階段佩憾,該算法并沒有直接對(duì)死亡的對(duì)象進(jìn)行清理,而是將所有存活的對(duì)象整理一下干花,放到另一處空間妄帘,然后把剩下的所有對(duì)象全部清除。這樣就達(dá)到了標(biāo)記-整理的目的池凄。
優(yōu)點(diǎn)
該算法不會(huì)像標(biāo)記-清除算法那樣產(chǎn)生大量的碎片空間抡驼。
缺點(diǎn)
如果存活的對(duì)象過多,整理階段將會(huì)執(zhí)行較多復(fù)制操作肿仑,導(dǎo)致算法效率降低致盟。
復(fù)制算法
該算法將內(nèi)存平均分成兩部分,然后每次只使用其中的一部分尤慰,當(dāng)這部分內(nèi)存滿的時(shí)候馏锡,將內(nèi)存中所有存活的對(duì)象復(fù)制到另一個(gè)內(nèi)存中,然后將之前的內(nèi)存清空伟端,只使用這部分內(nèi)存杯道,循環(huán)下去。
注意:
這個(gè)算法與標(biāo)記-整理算法的區(qū)別在于责蝠,該算法不是在同一個(gè)區(qū)域復(fù)制党巾,而是將所有存活的對(duì)象復(fù)制到另一個(gè)區(qū)域內(nèi)。
優(yōu)點(diǎn)
實(shí)現(xiàn)簡(jiǎn)單霜医;不產(chǎn)生內(nèi)存碎片
缺點(diǎn)
每次運(yùn)行齿拂,總有一半內(nèi)存是空的,導(dǎo)致可使用的內(nèi)存空間只有原來的一半肴敛。
分代收集算法
現(xiàn)在的虛擬機(jī)垃圾收集大多采用這種方式署海,它根據(jù)對(duì)象的生存周期,將堆分為新生代(Young)和老年代(Tenure)。在新生代中叹侄,由于對(duì)象生存期短巩搏,每次回收都會(huì)有大量對(duì)象死去,那么這時(shí)就采用復(fù)制算法趾代。老年代里的對(duì)象存活率較高贯底,沒有額外的空間進(jìn)行分配擔(dān)保,所以可以使用標(biāo)記-整理 或者 標(biāo)記-清除撒强。
具體過程:新生代(Young)分為Eden區(qū)禽捆,F(xiàn)rom區(qū)與To區(qū)
當(dāng)系統(tǒng)創(chuàng)建一個(gè)對(duì)象的時(shí)候,總是在Eden區(qū)操作飘哨,當(dāng)這個(gè)區(qū)滿了胚想,那么就會(huì)觸發(fā)一次YoungGC,也就是年輕代的垃圾回收芽隆。一般來說這時(shí)候不是所有的對(duì)象都沒用了浊服,所以就會(huì)把還能用的對(duì)象復(fù)制到From區(qū)。
這樣整個(gè)Eden區(qū)就被清理干凈了胚吁,可以繼續(xù)創(chuàng)建新的對(duì)象牙躺,當(dāng)Eden區(qū)再次被用完,就再觸發(fā)一次YoungGC腕扶,然后呢孽拷,注意,這個(gè)時(shí)候跟剛才稍稍有點(diǎn)區(qū)別半抱。這次觸發(fā)YoungGC后脓恕,會(huì)將Eden區(qū)與From區(qū)還在被使用的對(duì)象復(fù)制到To區(qū)。
再下一次YoungGC的時(shí)候窿侈,則是將Eden區(qū)與To區(qū)中的還在被使用的對(duì)象復(fù)制到From區(qū)炼幔。
經(jīng)過若干次YoungGC后,有些對(duì)象在From與To之間來回游蕩史简,這時(shí)候From區(qū)與To區(qū)亮出了底線(閾值)江掩,這些家伙要是到現(xiàn)在還沒掛掉,對(duì)不起乘瓤,一起滾到(復(fù)制)老年代吧。
老年代經(jīng)過這么幾次折騰策泣,也就扛不住了(空間被用完)衙傀,好,那就來次集體大掃除(Full GC)萨咕,也就是全量回收统抬。如果Full GC使用太頻繁的話,無疑會(huì)對(duì)系統(tǒng)性能產(chǎn)生很大的影響。所以要合理設(shè)置年輕代與老年代的大小聪建,盡量減少Full GC的操作钙畔。
垃圾收集器
如果說收集算法是內(nèi)存回收的方法論,垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)金麸。
- Serial收集器
串行收集器是最古老擎析,最穩(wěn)定以及效率高的收集器
可能會(huì)產(chǎn)生較長(zhǎng)的停頓,只使用一個(gè)線程去回收
-XX:+UseSerialGC
新生代挥下、老年代使用串行回收
新生代復(fù)制算法
老年代標(biāo)記-壓縮
- 并行收集器
1.ParNew
-XX:+UseParNewGC(new代表新生代揍魂,所以適用于新生代)
新生代并行
老年代串行
Serial收集器新生代的并行版本
在新生代回收時(shí)使用復(fù)制算法
多線程,需要多核支持
-XX:ParallelGCThreads 限制線程數(shù)量
2.Parallel
類似ParNew
新生代復(fù)制算法
老年代標(biāo)記-壓縮
更加關(guān)注吞吐量
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 老年代并行
3.其他GC參數(shù)
-XX:MaxGCPauseMills
最大停頓時(shí)間棚瘟,單位毫秒
GC盡力保證回收時(shí)間不超過設(shè)定值
-XX:GCTimeRatio
0-100的取值范圍
垃圾收集時(shí)間占總時(shí)間的比
默認(rèn)99现斋,即最大允許1%時(shí)間做GC
這兩個(gè)參數(shù)是矛盾的。因?yàn)橥nD時(shí)間和吞吐量不可能同時(shí)調(diào)優(yōu)
- CMS收集器
Concurrent Mark Sweep 并發(fā)標(biāo)記清除(應(yīng)用程序線程和GC線程交替執(zhí)行)
使用標(biāo)記-清除算法
并發(fā)階段會(huì)降低吞吐量(停頓時(shí)間減少偎蘸,吞吐量降低)
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
CMS運(yùn)行過程比較復(fù)雜庄蹋,著重實(shí)現(xiàn)了標(biāo)記的過程,可分為
初始標(biāo)記(會(huì)產(chǎn)生全局停頓)
根可以直接關(guān)聯(lián)到的對(duì)象
速度快并發(fā)標(biāo)記(和用戶線程一起)
主要標(biāo)記過程迷雪,標(biāo)記全部對(duì)象重新標(biāo)記 (會(huì)產(chǎn)生全局停頓)
由于并發(fā)標(biāo)記時(shí)限书,用戶線程依然運(yùn)行,因此在正式清理前振乏,再做修正蔗包。-
并發(fā)清除(和用戶線程一起)
基于標(biāo)記結(jié)果,直接清理對(duì)象
這里就能很明顯的看出慧邮,為什么CMS要使用標(biāo)記清除而不是標(biāo)記壓縮调限,如果使用標(biāo)記壓縮,需要多對(duì)象的內(nèi)存位置進(jìn)行改變误澳,這樣程序就很難繼續(xù)執(zhí)行耻矮。但是標(biāo)記清除會(huì)產(chǎn)生大量?jī)?nèi)存碎片,不利于內(nèi)存分配忆谓。
CMS收集器特點(diǎn):
盡可能降低停頓
會(huì)影響系統(tǒng)整體吞吐量和性能
比如裆装,在用戶線程運(yùn)行過程中,分一半CPU去做GC倡缠,系統(tǒng)性能在GC階段哨免,反應(yīng)速度就下降一半
清理不徹底
因?yàn)樵谇謇黼A段,用戶線程還在運(yùn)行昙沦,會(huì)產(chǎn)生新的垃圾琢唾,無法清理
因?yàn)楹陀脩艟€程一起運(yùn)行,不能在空間快滿時(shí)再清理(因?yàn)橐苍S在并發(fā)GC的期間盾饮,用戶線程又申請(qǐng)了大量?jī)?nèi)存采桃,導(dǎo)致內(nèi)存不夠)
-XX:CMSInitiatingOccupancyFraction設(shè)置觸發(fā)GC的閾值
如果不幸內(nèi)存預(yù)留空間不夠懒熙,就會(huì)引起concurrent mode failure
一旦 concurrent mode failure產(chǎn)生,將使用串行收集器作為后備普办。
CMS也提供了整理碎片的參數(shù):
-XX:+ UseCMSCompactAtFullCollection Full GC后工扎,進(jìn)行一次整理
整理過程是獨(dú)占的,會(huì)引起停頓時(shí)間變長(zhǎng)
-XX:+CMSFullGCsBeforeCompaction
設(shè)置進(jìn)行幾次Full GC后衔蹲,進(jìn)行一次碎片整理
-XX:ParallelCMSThreads
設(shè)定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量)
CMS的提出是想改善GC的停頓時(shí)間肢娘,在GC過程中的確做到了減少GC時(shí)間,但是同樣導(dǎo)致產(chǎn)生大量?jī)?nèi)存碎片踪危,又需要消耗大量時(shí)間去整理碎片蔬浙,從本質(zhì)上并沒有改善時(shí)間。
- G1收集器
G1是目前技術(shù)發(fā)展的最前沿成果之一贞远,HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是未來可以替換掉JDK1.5中發(fā)布的CMS收集器畴博。
與CMS收集器相比G1收集器有以下特點(diǎn):
(1) 空間整合,G1收集器采用標(biāo)記整理算法蓝仲,不會(huì)產(chǎn)生內(nèi)存空間碎片俱病。分配大對(duì)象時(shí)不會(huì)因?yàn)闊o法找到連續(xù)空間而提前觸發(fā)下一次GC。
(2)可預(yù)測(cè)停頓袱结,這是G1的另一大優(yōu)勢(shì)亮隙,降低停頓時(shí)間是G1和CMS的共同關(guān)注點(diǎn),但G1除了追求低停頓外垢夹,還能建立可預(yù)測(cè)的停頓時(shí)間模型溢吻,能讓使用者明確指定在一個(gè)長(zhǎng)度為N毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒果元,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了促王。
上面提到的垃圾收集器,收集的范圍都是整個(gè)新生代或者老年代而晒,而G1不再是這樣蝇狼。使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大差別倡怎,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)迅耘,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了监署,它們都是一部分(可以不連續(xù))Region的集合颤专。
G1的新生代收集跟ParNew類似,當(dāng)新生代占用達(dá)到一定比例的時(shí)候钠乏,開始出發(fā)收集血公。
和CMS類似,G1收集器收集老年代對(duì)象會(huì)有短暫停頓缓熟。
步驟:
(1)標(biāo)記階段累魔,首先初始標(biāo)記(Initial-Mark),這個(gè)階段是停頓的(Stop the World Event),并且會(huì)觸發(fā)一次普通Mintor GC够滑。對(duì)應(yīng)GC log:GC pause (young) (inital-mark)
(2)Root Region Scanning垦写,程序運(yùn)行過程中會(huì)回收survivor區(qū)(存活到老年代),這一過程必須在young GC之前完成彰触。
(3)Concurrent Marking梯投,在整個(gè)堆中進(jìn)行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行),此過程可能被young GC中斷况毅。在并發(fā)標(biāo)記階段分蓖,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑?duì)象都是垃圾,那個(gè)這個(gè)區(qū)域會(huì)被立即回收(圖中打X)尔许。同時(shí)么鹤,并發(fā)標(biāo)記過程中,會(huì)計(jì)算每個(gè)區(qū)域的對(duì)象活性(區(qū)域中存活對(duì)象的比例)味廊。
(4)Remark, 再標(biāo)記蒸甜,會(huì)有短暫停頓(STW)。再標(biāo)記階段是用來收集 并發(fā)標(biāo)記階段 產(chǎn)生新的垃圾(并發(fā)階段和應(yīng)用程序一同運(yùn)行)余佛;G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)柠新。
(5)Copy/Clean up,多線程清除失活對(duì)象辉巡,會(huì)有STW恨憎。G1將回收區(qū)域的存活對(duì)象拷貝到新區(qū)域,清除Remember Sets郊楣,并發(fā)清空回收區(qū)域并把它返回到空閑區(qū)域鏈表中憔恳。
(6)復(fù)制/清除過程后×「剩回收區(qū)域的活性對(duì)象已經(jīng)被集中回收到深藍(lán)色和深綠色區(qū)域喇嘱。
finalize()方法詳解
- finalize的作用
(1)finalize()是Object的protected方法,子類可以覆蓋該方法以實(shí)現(xiàn)資源清理工作塞栅,GC在回收對(duì)象之前調(diào)用該方法者铜。
(2)finalize()與C++中的析構(gòu)函數(shù)不是對(duì)應(yīng)的。C++中的析構(gòu)函數(shù)調(diào)用的時(shí)機(jī)是確定的(對(duì)象離開作用域或delete掉)放椰,但Java中的finalize的調(diào)用具有不確定性
(3)不建議用finalize方法完成“非內(nèi)存資源”的清理工作作烟,但建議用于:① 清理本地對(duì)象(通過JNI創(chuàng)建的對(duì)象);② 作為確保某些非內(nèi)存資源(如Socket砾医、文件等)釋放的一個(gè)補(bǔ)充:在finalize方法中顯式調(diào)用其他資源釋放方法拿撩。其原因可見下文[finalize的問題]
finalize的問題
(1)一些與finalize相關(guān)的方法,由于一些致命的缺陷如蚜,已經(jīng)被廢棄了压恒,如System.runFinalizersOnExit()方法影暴、Runtime.runFinalizersOnExit()方法
(2)System.gc()與System.runFinalization()方法增加了finalize方法執(zhí)行的機(jī)會(huì),但不可盲目依賴它們
(3)Java語言規(guī)范并不保證finalize方法會(huì)被及時(shí)地執(zhí)行探赫、而且根本不會(huì)保證它們會(huì)被執(zhí)行
(4)finalize方法可能會(huì)帶來性能問題型宙。因?yàn)镴VM通常在單獨(dú)的低優(yōu)先級(jí)線程中完成finalize的執(zhí)行
(5)對(duì)象再生問題:finalize方法中,可將待回收對(duì)象賦值給GC Roots可達(dá)的對(duì)象引用伦吠,從而達(dá)到對(duì)象再生的目的
(6)finalize方法至多由GC執(zhí)行一次(用戶當(dāng)然可以手動(dòng)調(diào)用對(duì)象的finalize方法妆兑,但并不影響GC對(duì)finalize的行為)finalize的執(zhí)行過程(生命周期)
(1) 首先,大致描述一下finalize流程:當(dāng)對(duì)象變成(GC Roots)不可達(dá)時(shí)毛仪,GC會(huì)判斷該對(duì)象是否覆蓋了finalize方法搁嗓,若未覆蓋,則直接將其回收箱靴。否則腺逛,若對(duì)象未執(zhí)行過finalize方法,將其放入F-Queue隊(duì)列刨晴,由一低優(yōu)先級(jí)線程執(zhí)行該隊(duì)列中對(duì)象的finalize方法屉来。執(zhí)行finalize方法完畢后,GC會(huì)再次判斷該對(duì)象是否可達(dá)狈癞,若不可達(dá)茄靠,則進(jìn)行回收,否則蝶桶,對(duì)象“復(fù)活”慨绳。
(2) 具體的finalize流程:
對(duì)象可由兩種狀態(tài),涉及到兩類狀態(tài)空間真竖,一是終結(jié)狀態(tài)空間 F = {unfinalized, finalizable, finalized}脐雪;二是可達(dá)狀態(tài)空間 R = {reachable, finalizer-reachable, unreachable}。各狀態(tài)含義如下:
unfinalized: 新建對(duì)象會(huì)先進(jìn)入此狀態(tài)恢共,GC并未準(zhǔn)備執(zhí)行其finalize方法战秋,因?yàn)樵搶?duì)象是可達(dá)的
finalizable: 表示GC可對(duì)該對(duì)象執(zhí)行finalize方法,GC已檢測(cè)到該對(duì)象不可達(dá)讨韭。正如前面所述脂信,GC通過F-Queue隊(duì)列和一專用線程完成finalize的執(zhí)行
finalized: 表示GC已經(jīng)對(duì)該對(duì)象執(zhí)行過finalize方法
reachable: 表示GC Roots引用可達(dá)
finalizer-reachable(f-reachable):表示不是reachable,但可通過某個(gè)finalizable對(duì)象可達(dá)
unreachable:對(duì)象不可通過上面兩種途徑可達(dá)
變遷說明:
(1)新建對(duì)象首先處于[reachable, unfinalized]狀態(tài)(A)
(2)隨著程序的運(yùn)行透硝,一些引用關(guān)系會(huì)消失狰闪,導(dǎo)致狀態(tài)變遷,從reachable狀態(tài)變遷到f-reachable(B, C, D)或unreachable(E, F)狀態(tài)
(3)若JVM檢測(cè)到處于unfinalized狀態(tài)的對(duì)象變成f-reachable或unreachable濒生,JVM會(huì)將其標(biāo)記為finalizable狀態(tài)(G,H)埋泵。若對(duì)象原處于[unreachable, unfinalized]狀態(tài),則同時(shí)將其標(biāo)記為f-reachable(H)。
(4)在某個(gè)時(shí)刻丽声,JVM取出某個(gè)finalizable對(duì)象礁蔗,將其標(biāo)記為finalized并在某個(gè)線程中執(zhí)行其finalize方法。由于是在活動(dòng)線程中引用了該對(duì)象雁社,該對(duì)象將變遷到(reachable, finalized)狀態(tài)(K或J)瘦麸。該動(dòng)作將影響某些其他對(duì)象從f-reachable狀態(tài)重新回到reachable狀態(tài)(L, M, N)
(5)處于finalizable狀態(tài)的對(duì)象不能同時(shí)是unreahable的,由第4點(diǎn)可知歧胁,將對(duì)象finalizable對(duì)象標(biāo)記為finalized時(shí)會(huì)由某個(gè)線程執(zhí)行該對(duì)象的finalize方法,致使其變成reachable厉碟。這也是圖中只有八個(gè)狀態(tài)點(diǎn)的原因
(6)程序員手動(dòng)調(diào)用finalize方法并不會(huì)影響到上述內(nèi)部標(biāo)記的變化喊巍,因此JVM只會(huì)至多調(diào)用finalize一次,即使該對(duì)象“復(fù)活”也是如此箍鼓。程序員手動(dòng)調(diào)用多少次不影響JVM的行為
(7)若JVM檢測(cè)到finalized狀態(tài)的對(duì)象變成unreachable崭参,回收其內(nèi)存(I)
(8)若對(duì)象并未覆蓋finalize方法,JVM會(huì)進(jìn)行優(yōu)化款咖,直接回收對(duì)象(O)
(9)注:System.runFinalizersOnExit()等方法可以使對(duì)象即使處于reachable狀態(tài)何暮,JVM仍對(duì)其執(zhí)行finalize方法
總結(jié)
根據(jù)GC的工作原理,我們可以通過一些技巧和方式铐殃,讓GC運(yùn)行更加有效率海洼,更加符合應(yīng)用程序的要求。一些關(guān)于程序設(shè)計(jì)的幾點(diǎn)建議:
1.最基本的建議就是盡早釋放無用對(duì)象的引用富腊。大多數(shù)程序員在使用臨時(shí)變量的時(shí)候坏逢,都是讓引用變量在退出活動(dòng)域(scope)后,自動(dòng)設(shè)置為 null.我們?cè)谑褂眠@種方式時(shí)候赘被,必須特別注意一些復(fù)雜的對(duì)象圖是整,例如數(shù)組,隊(duì)列民假,樹浮入,圖等,這些對(duì)象之間有相互引用關(guān)系較為復(fù)雜羊异。對(duì)于這類對(duì)象事秀,GC 回收它們一般效率較低。如果程序允許球化,盡早將不用的引用對(duì)象賦為null.這樣可以加速GC的工作秽晚。
2.盡量少用finalize函數(shù)。finalize函數(shù)是Java提供給程序員一個(gè)釋放對(duì)象或資源的機(jī)會(huì)筒愚。但是赴蝇,它會(huì)加大GC的工作量,因此盡量少采用finalize方式回收資源巢掺。
3.如果需要使用經(jīng)常使用的圖片句伶,可以使用soft應(yīng)用類型劲蜻。它可以盡可能將圖片保存在內(nèi)存中,供程序調(diào)用考余,而不引起OutOfMemory.
4.注意集合數(shù)據(jù)類型先嬉,包括數(shù)組,樹楚堤,圖疫蔓,鏈表等數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)對(duì)GC來說身冬,回收更為復(fù)雜衅胀。另外,注意一些全局的變量酥筝,以及一些靜態(tài)變量滚躯。這些變量往往容易引起懸掛對(duì)象(dangling reference),造成內(nèi)存浪費(fèi)嘿歌。
5.當(dāng)程序有一定的等待時(shí)間掸掏,程序員可以手動(dòng)執(zhí)行System.gc(),通知GC運(yùn)行宙帝,但是Java語言規(guī)范并不保證GC一定會(huì)執(zhí)行丧凤。使用增量式GC可以縮短Java程序的暫停時(shí)間。
參考
https://blog.csdn.net/laomo_bible/article/details/83112622
https://blog.csdn.net/weixin_33923762/article/details/88030825
https://blog.csdn.net/jisuanjiguoba/article/details/80156781