34.对象 GC,GC属性,影响GC的因素,GC步骤,GC算法,安全区/安全区域,新生代,老年代等介绍
34.對象 GC,GC屬性,影響GC的因素,GC步驟,GC算法,安全區(qū)/安全區(qū)域,新生代,老年代等介紹
34.1.GC屬性
34.2.影響GC的因素
34.3.GC步驟
34.3.1.Mark
34.3.1.1.引用計數(shù)算法(Reference Counting)
34.3.1.2.可達性分析算法(Reachability Analysis)
34.3.2.Clean Up
34.3.2.1.清除(Sweep)
34.3.2.2.復(fù)制(Copying)
34.3.2.3.整理(Compacting)
34.4.分代回收(Generational)
34.4.1.1.YOUNG 新生代
34.4.1.2.OLD 老年代
34.4.1.3.PERM 永久代
34.5.安全點/安全區(qū)域
34.6.垃圾收集器
34.6.1.YOUNG 新生代
34.6.1.1.Serial(-XX:+UseSerialGC)
34.6.1.2.ParNew(-XX:+UseParNewGC)
34.6.1.3.Parallel Scavenge(-XX:+UseParallelGC)
34.6.2.OLD 老年代
34.6.2.1.Serial Old
34.6.2.2.Parallel Old(-XX:+UseParallelOldGC)
34.6.2.3.CMS(-XX:+UseConcMarkSweepGC)
34.6.2.4.G1(Garbage First,-XX:+UseG1GC)
34.7.GC監(jiān)控
34.對象 GC,GC屬性,影響GC的因素,GC步驟,GC算法,安全區(qū)/安全區(qū)域,新生代,老年代等介紹
Java的一大特性就是內(nèi)存的分配和回收都是自動進行的。當程序規(guī)模不大時,我們完全可以不考慮內(nèi)存的使用情況。但是一旦程序的規(guī)模足夠大,對性能的要求足夠高時,了解Java垃圾收集(GC)的內(nèi)部機制并根據(jù)具體的應(yīng)用特征來調(diào)整使用的垃圾收集算法就顯得十分重要了。
34.1.GC屬性
吞吐量(Throughput):程序運行時間 /(程序運行時間 + 垃圾收集時間)
延遲(Latency):使程序盡可能少的因為垃圾回收而暫停的能力
足跡(Footprint):GC運行時使用的內(nèi)存空間
敏捷度(Promptness):對象被標記為死亡到對象所占內(nèi)存被回收所經(jīng)歷的時間
34.2.影響GC的因素
堆大小
堆內(nèi)對象的存活率
內(nèi)存分配速率
引用更新速率
對象的壽命
34.3.GC步驟
34.3.1.Mark
標記階段,目的是將不可用的對象標記出來,以便進行后階段的回收。那么,如何判斷一個對象是否可用呢?這跟指向該對象的引用有很大的關(guān)系。因此,在具體研究對象可用性判定算法之前,讓我們先看一看Java中不同的引用類型。
Java引用類型
Java中主要有以下四種引用類型:
?強引用(Strong Reference):大多數(shù)情況下使用的引用類型。如Object obj = new Object();中的obj就屬于強引用。被強引用引用的對象在任何時候都不能被回收。
?軟引用(Soft Reference):使用SoftReference類創(chuàng)建的引用,其所引用的對象將在內(nèi)存空間不足時被回收。
?弱引用(Weak Reference):使用WeakRe引用,不管內(nèi)存空間是否足夠,其所引用的對象都將在下一次GC時被回收。ference類創(chuàng)建的
?虛引用(Phantom Reference):使用PhantomReference類創(chuàng)建的引用,不影響其所引用的對象的壽命,僅在被回收時收到一個系統(tǒng)通知
上述四種引用的強度由上至下依次減弱。可以看出,除了強引用,其他引用對于GC的執(zhí)行并無太大的影響。因此,以下討論中談到的引用均指強引用。
34.3.1.1.引用計數(shù)算法(Reference Counting)
該算法給每個對象添加一個引用計數(shù)器,當有引用指向?qū)ο髸r,計數(shù)器加1,當引用失效時,計數(shù)器減1。因此,當一個對象的引用計數(shù)變?yōu)?時,就證明該對象不可用,其所占用的內(nèi)存也可以立即被釋放。
但是主流的Java虛擬機中并沒有使用這一簡單高效的算法來管理內(nèi)存,主要原因就是它無法解決循環(huán)引用(Circular)的問題。也即,當對象A和對象B相互引用,而沒有任何其他對象指向A和B時,由于A和B的引用計數(shù)均為1(不等于0),引用計數(shù)算法將無法回收這兩個對象。
同時,該算法對引用計數(shù)的頻繁更新也會使得效率降低。
34.3.1.2.可達性分析算法(Reachability Analysis)
從一系列名為”GC Roots”的對象開始向下搜索,就可以形成若干條引用鏈。如果一個對象到”GC Roots”無任何引用鏈相連,該對象則被判定為可回收對象。
可以作為GC Roots的對象包括以下幾類:
?虛擬機棧中引用的對象
?方法區(qū)中類靜態(tài)屬性引用的對象
?方法區(qū)中常量引用的對象
?Native方法引用的對象
該算法可以很好的解決循環(huán)引用的問題。同時,對于高度變化的程序來說比引用計數(shù)法效率更高。但是,在迅速發(fā)現(xiàn)不可用的對象方面,則沒有引用計數(shù)法那么快。
34.3.2.Clean Up
清理階段。將Mark階段標記出的不可用對象清除,釋放其所占用的內(nèi)存空間。主要有以下幾種實現(xiàn)方式。
34.3.2.1.清除(Sweep)
算法思想:遍歷堆空間,將Mark階段標記不可用的對象清除。
不足: 效率不高;空間問題,多次清除之后會產(chǎn)生大量的內(nèi)存碎片。
適用場景:對象壽命長的內(nèi)存區(qū)域。
該算法過程如下圖所示:
34.3.2.2.復(fù)制(Copying)
算法思想:將內(nèi)存劃分為兩個區(qū)域(大小比例可調(diào)整),每次只用其中一塊,當此塊內(nèi)存用完時,就將存活對象復(fù)制到另一塊內(nèi)存中,并對當前塊進行內(nèi)存回收。
優(yōu)點:解決了內(nèi)存碎片問題;內(nèi)存分配效率提高。每次復(fù)制后對象在堆中都是線性排列的,因此內(nèi)存分配時只需移動堆頂指針即可。
不足:如果對象的存活率較高,大量的復(fù)制操作會顯著的降低效率;內(nèi)存空間浪費,每次都只能使用堆空間的一部分,代價高昂。
該算法過程如下圖所示:
34.3.2.3.整理(Compacting)
算法思想:將標記的所有可用對象向內(nèi)存一端移動,然后直接清理邊界以外的內(nèi)存區(qū)域即可。
優(yōu)點:類似于復(fù)制算法,解決了內(nèi)存碎片問題,內(nèi)存分配效率提高;消除了復(fù)制算法對內(nèi)存空間的浪費。
不足:難以做到并行。
該算法過程如下圖所示:
34.4.分代回收(Generational)
前面所述的Mark-Clean算法都是針對整個堆區(qū)域的,每一次GC運行都需要對堆中所有的對象進行遍歷。因此,隨著堆中對象數(shù)量的增多,GC的效率就會隨之下降。于是,GC對程序運行做出如下假設(shè):
?大多數(shù)對象都會在創(chuàng)建后不久死亡
?如果對象已存活一段時間,那它很可能會繼續(xù)存活一段時間
基于這兩個假設(shè),GC將堆中的對象按照存活時間分為三代:Young(新生代)、Old(老年代)、Perm(永久代)。其內(nèi)存劃分示意圖如下:
34.4.1.1.YOUNG 新生代
由圖可見,新生代又可劃分為三個區(qū)域:Eden,Survivor0,Survivor1。其中,Eden區(qū)最大,新對象的內(nèi)存分配都在此區(qū)域進行。兩個Survivor區(qū)域一個為From區(qū),一個為To區(qū),每次只使用其中的一個。
新生代的垃圾回收采用的是復(fù)制算法。第一次GC時,Eden區(qū)的存活對象會被復(fù)制到S0區(qū)。此后每次進行GC時,Eden區(qū)和From區(qū)的存活對象都會被復(fù)制到To區(qū)。如果一個對象在經(jīng)歷了幾次垃圾回收后仍然存活,那么它就會被復(fù)制到Old Generation(老年代),此過程稱為Promotion。
34.4.1.2.OLD 老年代
老年代的對象是由新生代對象經(jīng)過Promotion而來,基于前面列出的假設(shè):“如果對象已存活一段時間,那它很可能會繼續(xù)存活一段時間”,該區(qū)域的對象存活率普遍較高,因此一般采用Mark-Sweep或Mark-Compact算法。
34.4.1.3.PERM 永久代
永久代并不用來存儲從老年代經(jīng)過Promotion而來的對象,它存儲的是元數(shù)據(jù),包括已被虛擬機加載的類信息、常量、靜態(tài)變量、方法等。該區(qū)域通常不會發(fā)生垃圾回收。
34.5.安全點/安全區(qū)域
在程序執(zhí)行時,并非任何時候都可以停下來進行垃圾回收,只有到達某些特定的點時才能暫停,這些點稱為安全點(Safepoint)。安全點的設(shè)定既不能太少以致于讓GC等待時間過長,也不能太頻繁導(dǎo)致運行時負荷增大。一般在方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)處會產(chǎn)生安全點。
那么,如何在GC發(fā)生時讓所有的用戶線程都“跑”到最近的安全點上停下來呢,有以下兩種方案:
1、搶先式中斷:在GC發(fā)生時即中斷所有用戶線程,若有的線程中斷的地方不是安全點,則恢復(fù)該線程,讓它跑到安全點上再暫停。(幾乎不用)
2、主動式中斷:GC在其要開始運行前設(shè)置一個標志。而每個用戶線程在運行過程中都會去主動的輪詢這個標志,如果標志為真則主動中斷掛起。由于輪詢標志的地方和安全點重合,因此線程暫停的地方一定是安全的。
但是,以上實現(xiàn)方案有一種情況無法解決,那就是用戶線程不運行的時候,也即處于sleep或blocked狀態(tài)的時候。由于此時線程無法輪詢中斷標志,也就不能保證GC開始時它一定處于安全狀態(tài)。此時就需要引入安全區(qū)域(Safe Region)的概念了,它是指在一段代碼片段中,對象之間的引用關(guān)系不會發(fā)生變化。安全區(qū)域可以看做是擴展了的安全點。
當用戶線程執(zhí)行到Safe Region時,首先會標志自己進入了安全區(qū)域。那么,就算GC要開始時該線程處于blocked狀態(tài),GC也可以放心的執(zhí)行垃圾回收動作了。而當線程要離開Safe Region時,要先檢查GC是否已經(jīng)完成。如果完成了,線程就可以繼續(xù)執(zhí)行,否則需等待直到收到可以安全離開Safe Region的信號為止。
34.6.垃圾收集器
不同的虛擬機中通常有不止一種的垃圾收集器,它們實現(xiàn)了不同的垃圾收集算法。以下列舉在Sun HotSpot虛擬機中包含的垃圾收集器。
34.6.1.YOUNG 新生代
34.6.1.1.Serial(-XX:+UseSerialGC)
單線程收集器,采用復(fù)制算法。GC運行時會暫停所有的用戶線程(STW,Stop The World)。是虛擬機運行在Client模式下的默認新生代收集器。
34.6.1.2.ParNew(-XX:+UseParNewGC)
Serial收集器的多線程版本,除此之外與Serial收集器幾乎完全相同。是許多運行在Server模式下的虛擬機中首選的新生代收集器。原因之一是它是唯一能與CMS配合使用的新生代收集器。
可使用-XX:ParallelGCThreads參數(shù)指定垃圾收集的線程數(shù)。
34.6.1.3.Parallel Scavenge(-XX:+UseParallelGC)
與ParNew一樣,是使用復(fù)制算法的多線程收集器。但是不同于ParNew對縮短垃圾收集時用戶線程停頓時間的關(guān)注,Parallel Scavenge更多的是關(guān)注提高程序的吞吐量,因此常被稱為“吞吐量優(yōu)先”收集器。適用于在后臺運算而沒有太多交互的任務(wù)。
34.6.2.OLD 老年代
34.6.2.1.Serial Old
Serial收集器的老年代版本,單線程收集器,使用Mark-Compact算法。
主要用于Client模式下的虛擬機。但在Server模式下也有兩大用途。
?在JDK1.5及之前版本中與Parallel Scavenge搭配使用
?作為CMS收集器的后備預(yù)案,在發(fā)生Concurrent Mode Failure時使用Parallel Old(-XX:+UseParallelOldGC)
34.6.2.2.Parallel Old(-XX:+UseParallelOldGC)
Parallel Scavenge的老年代版本,使用多線程和Mark-Compact算法,JDK1.6開始提供。
下圖展示了Serial和Parallel收集器的工作模式。
34.6.2.3.CMS(-XX:+UseConcMarkSweepGC)
Concurrent Mark Sweep,其工作過程可分為以下四個步驟:
?初始標記(Initial Mark):STW方式。標記GC Roots直接引用的對象,時間很短。
?并發(fā)標記(Concurrent Mark):進行GC Roots Tracing,標記出所有可用對象。
?重新標記(Remark):對并發(fā)標記期間因程序繼續(xù)運行而變化的引用進行修正,停頓時間比初始標記長,但遠比并發(fā)標記短。
?并發(fā)清除(Concurrent Sweep):清除不可用對象,釋放內(nèi)存。
該過程示意圖如下所示:
由圖可見,CMS執(zhí)行過程中大部分階段都是與用戶線程并行進行的,因此用戶線程暫停時間會大大減少。但是由于CMS在進行清理時,用戶線程也在運行,也即此時仍然會有新的垃圾產(chǎn)生。這些垃圾稱為“浮動垃圾”(Floating Garbage)。由于“浮動垃圾”產(chǎn)生于CMS標記階段之后,它們只能等到下一次GC時才可被回收。所以,CMS并不能等到老年代幾乎要滿了才開始垃圾收集動作,它必須預(yù)留足夠的空間給用戶線程在垃圾收集過程中使用。如果預(yù)留的空間預(yù)估不準的話,就有可能出現(xiàn)以下兩種情況:
?Concurrent Mode Failure:在CMS運行期間預(yù)留的內(nèi)存空間不夠用戶線程使用,這將觸發(fā)一次Full GC,即啟動后備預(yù)案(Serial Old收集器)來重新進行老年代的垃圾收集。這可能會導(dǎo)致數(shù)分鐘的用戶線程停頓。
?Promotion Failure:由于CMS采用的是Mark-Sweep算法,因此在執(zhí)行了幾次GC之后老年代會存在大量的內(nèi)存碎片。如果從新生代經(jīng)過Promotion而來的對象過大,就很有可能找不到足夠的空間來分配。這也會提前觸發(fā)一次Full GC。
可使用-XX:+CMSInitiatingOccupancyFraction參數(shù)來指定在老年代空間被使用多少后觸發(fā)垃圾收集,默認為68%。
34.6.2.4.G1(Garbage First,-XX:+UseG1GC)
之所以把G1單獨列出來,是因為它在內(nèi)存年代劃分上不同于上面介紹的所有收集器。G1把內(nèi)存分為很多個大小相等的獨立區(qū)域(Region),新生代和老年代不再是相互隔離的,而是都由若干個非連續(xù)的Region組成。除此之外,G1收集器還有以下幾個特點:
?并行與并發(fā):可大大縮短STW的時間
?分代收集:G1可以不需要其他收集器的配合而獨立管理整個GC堆,而且它能夠使用不同的方式去處理不同年代的對象
?空間整合:G1整體上采用Mark-Compact算法,消除了內(nèi)存碎片
?可預(yù)測的停頓:通過跟蹤各個Region中垃圾堆積的價值大小(可回收的空間大小以及回收所需時間的經(jīng)驗值),G1維護了一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先對價值最大的Region進行回收。
當然,由于跨Region引用的存在,垃圾收集并不能真的以Region為單位進行。對于這種情況,G1通過為每一個Region維護一個Remember Set(RSet)來避免進行全堆掃描。RSet中記錄了其他Region中的對象指向本Region對象的引用信息。
忽略RSet的維護操作,G1的執(zhí)行過程主要分為以下四步,其與CMS的執(zhí)行過程很相似:
1、初始標記(Initial Mark):STW方式。標記GC Roots直接引用的對象,并修改TAMS的值,使下一階段用戶線程并發(fā)運行時能夠在正確可用的Region中創(chuàng)建新對象。時間很短。
2、并發(fā)標記(Concurrent Mark):進行GC Roots Tracing,標記出所有可用對象。
3、最終標記(Final Mark):將并發(fā)標記期間因程序繼續(xù)運行而變化的引用合并到RSet中。
4、篩選回收(Live Data Counting and Evacuation):對各個Region按照回收價值和成本進行排序,然后根據(jù)用戶所期望的GC停頓時間來制定回收計劃。
其執(zhí)行過程如下圖所示:
34.7.GC監(jiān)控
在實際應(yīng)用中,常常需要根據(jù)不同的應(yīng)用特征調(diào)整垃圾收集器的配置方案。在調(diào)整過程中,不免需要監(jiān)控各種收集器的運行過程來進行性能的比較。JDK自帶了一個Visual VM工具來可視化GC的執(zhí)行過程。筆者最近為了跟蹤服務(wù)器上不同垃圾收集器實現(xiàn)的性能,分析了較多的GC日志。不同收集器生成的日志格式可能不盡相同,但都有一定的共性。下面列出的是在實際應(yīng)用中使用ParNew+CMS和使用G1時產(chǎn)生的日志,從中可以很清楚的看到CMS和G1的執(zhí)行階段以及GC運行時用戶線程暫停的時間,有興趣的朋友可以研究一下
ParNew+CMS日志
G1日志
使用的日志相關(guān)參數(shù)如下:
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC34.8.其他注意點
避免顯式調(diào)用System.gc()或Runtime.getRuntime().gc()。這兩個方法只是給虛擬機一個建議,是否執(zhí)行垃圾回收還是由虛擬機來決定。
不要在finalize()方法中釋放資源。
不要嘗試在finalize()方法中逃脫垃圾回收。
總結(jié)
以上是生活随笔為你收集整理的34.对象 GC,GC属性,影响GC的因素,GC步骤,GC算法,安全区/安全区域,新生代,老年代等介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求告知装修小白真诚发问,欧铂丽的全案定制
- 下一篇: 35.JVM 参数(JVM中的各种参数及