java 垃圾回收 新生代_Java垃圾回收
一、概述
Java垃圾回收器實現(xiàn)內(nèi)存的自動分配和回收,這兩個操作都發(fā)生在Java堆上(還包括方法區(qū),即永久代)。垃圾回收操作不是實時的發(fā)生(對象死亡不會立即釋放),當內(nèi)存消耗完或者是達到某一指標(threshold,使用內(nèi)存占總內(nèi)存的比列,比如0.75)時,觸發(fā)垃圾回收操作。有一個對象死亡的例外,java.lang.Thread類型的對象即使沒有引用,只要線程還在運行,就不會被回收。major collection會引發(fā)minor collection。
JVM中,程序計數(shù)器、虛擬機棧、本地方法棧有與線程是相同的生命周期。棧幀隨著方法的進入和退出做入棧和出棧操作,從而實現(xiàn)內(nèi)存的自動清理。因此,內(nèi)存垃圾回收主要集中于堆和方法區(qū)。
對象存活判斷方式
引用計數(shù):每個對象有一個引用計數(shù)屬性,新增一個引用時計數(shù)加1,引用釋放時計數(shù)減1,計數(shù)為0時可以回收。此方法簡單,但無法解決對象相互循環(huán)引用的問題。
可達性分析:從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。即為不可達對象。
在Java語言中,GC Roots包括:
虛擬機棧中引用的對象。
方法區(qū)中類靜態(tài)屬性引用的對象。
方法區(qū)中常量引用的對象。
本地方法棧中JNI引用的對象。
二、回收的算法
引用計數(shù)(reference counting)
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數(shù),刪除一個引用則減少一個計數(shù)。垃圾回收時,只用收集計數(shù)為0的對象。此算法最致命的是無法處理循環(huán)引用問題。
標記-清除(mark-sweep)
此算法執(zhí)行分兩階段。第一階段從引用根節(jié)點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,而且會產(chǎn)生內(nèi)存碎片。
復制(copying)
此算法把內(nèi)存空間劃為兩個相等的區(qū)域,每次只使用其中一個區(qū)域。垃圾回收時,遍歷當前使用區(qū)域,把正在使用中的對象復制到另外一個區(qū)域中。此算法每次只處理 正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內(nèi)存整理,不會出現(xiàn)“碎片”問題。此算法的缺點,就是需要兩倍內(nèi)存空間。
標記-整理(mark-compact)
此算法結合了“標記-清除”和“復制”兩個算法的優(yōu)點。是分兩階段,第一階段從根節(jié)點開始標記所有被引用對象,第二階段遍歷整個堆,清除未標記對象并且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“復制”算法的空間問題。
分代(generational collecting)
基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命周期的對象使用不同的算法(上述方式中的一個)進行回收?,F(xiàn)在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。
分區(qū)(G1)
三、回收的機制
依統(tǒng)計分析,大多數(shù)對象生命周期都是短暫的,所以把Java內(nèi)存分代管理。
四、回收的性能指標(Performance Metrics)
吞吐量(Throughput) 在一個較長的周期內(nèi),非回收時間占總時間的比率。
暫停時間(Pause time) Java虛擬機在回收垃圾的時候,有的算法會暫停所有應用線程的執(zhí)行。
五、回收的區(qū)域
年輕代(Young Generation)
年輕代被劃分為三個區(qū)域,伊甸區(qū)(eden)和兩個小的幸存區(qū)(survivor),兩個存活區(qū)按功能分為From和To。絕大多數(shù)的對象都在eden區(qū)分配,超過一個垃圾回收操作仍然存活的對象放到幸存區(qū)。當Eden區(qū)滿時,還存活的對象將被復制到survivor區(qū)(兩個中的一個),當這個survivor區(qū)滿時,此區(qū)的存活對象將被復制到另外一個survivor區(qū),當這個survivor區(qū)也滿了的時候,從第一個Survivor區(qū)復制過來的并且此時還存活的對象,將被復制“年老區(qū)(tenured)”。需要注意,survivor的兩個區(qū)是對稱的,沒先后關系,survivor區(qū)總有一個是空的。
年老代(Old Generation)
主要存儲年輕代中經(jīng)過多個回收周期仍然存活從而升級的對象,對于一些大的內(nèi)存分配,可以直接分配到永久代。
永久代(Permanent Generation)
存儲類、方法以及它們的描述信息。通常不需要調(diào)節(jié)該參數(shù),但是有些應用可能動態(tài)生成或者調(diào)用一些class, 這時需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。
六、回收的類型
minor GC
當新對象生成,在eden申請空間失敗時,就會觸發(fā)minor GC,對eden區(qū)域進行GC,清除非存活對象,并且把尚且存活的對象移動到survivor區(qū)。然后整理survivor的兩個區(qū)。
full GC
對整個堆進行整理,包括Young、Tenured和Perm。有如下原因可能導致Full GC:
- Tenured被寫滿
- Perm域被寫滿
- System.gc()被顯示調(diào)用
- 上一次GC之后heap的各域分配策略動態(tài)變化
七、垃圾回收器類型
串行收集器: 使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,也無法使用多處理器的優(yōu)勢,所以此收集器適合單處理器機器。當然,此收集器也可以用在小數(shù)據(jù)量(100M左右)情況下的多處理器機器上。使用-XX:+UseSerialGC打開。
并行收集器: 對年輕代進行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用。使用-XX:+UseParallelGC打開。并行收集器在J2SE5.0第6更新上引入,在Java SE6.0中進行了增強--可以對年老代進行并行收集。如果年老代不使用并發(fā)收集的話,是使用單線程進行垃圾回收,因此會制約擴展能力。使用-XX:+UseParallelOldGC打開。
使用-XX:ParallelGCThreads設置并行垃圾回收的線程數(shù)。此值可以設置與機器處理器數(shù)量相等。
此收集器可以進行如下配置:
§ 最大垃圾回收暫停: 指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis指定。為毫秒。如果指定了此值的話,堆大小和垃圾回收相關參數(shù)會進行調(diào)整以達到指定值。設定此值可能會減少應用的吞吐量。
§ 吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio=來設定,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用于垃圾回收。默認情況為99,即1%的時間用于垃圾回收。
并發(fā)收集器:可以保證大部分工作都并發(fā)進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的中、大規(guī)模應用。使用-XX:+UseConcMarkSweepGC打開。
1.主要減少年老代的暫停時間,在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集初期并發(fā)收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。
2. 并發(fā)收集器使用多處理器換來短暫的停頓時間。在一個N個處理器的系統(tǒng)上,并發(fā)收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。
3. 在只有一個處理器的主機上使用并發(fā)收集器,設置為incremental mode模式也可獲得較短的停頓時間。
4. 浮動垃圾:由于在應用運行的同時進行垃圾回收,所以會有些垃圾在垃圾回收完成時產(chǎn)生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所以,并發(fā)收集器一般需要20%的預留空間用于這些浮動垃圾。
5. Concurrent Mode Failure:并發(fā)收集器在應用運行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發(fā)生“并發(fā)模式失敗”,此時整個應用將會暫停,進行垃圾回收。
6. 啟動并發(fā)收集器:因為并發(fā)收集在應用運行時進行收集,所以必須保證收集完成之前有足夠的內(nèi)存空間供程序使用,否則會出現(xiàn)“Concurrent Mode Failure”。通過設置-XX:CMSInitiatingOccupancyFraction指定還有多少剩余堆時開始執(zhí)行并發(fā)收集。
八、具體的垃圾回收器
新生代
1 serial收集器
serial收集器是Hotspot運行在client模式下的默認新生代收集器, 它的特點是只用一個CPU/一條收集線程去完成GC工作, 且在進行垃圾收集時必須暫停其他所有的工作線程(“Stop The World” -后面簡稱STW)。
gc.serial.png
雖然是單線程收集, 但簡單而高效, 在VM管理內(nèi)存不大的情況下(收集幾十M~一兩百M的新生代), 停頓時間完全可以控制在幾十毫秒~一百多毫秒內(nèi).
2 ParNew收集器
ParNew收集器是前面serial的多線程版本, 除使用多個線程進行GC外, 包括Serial可用的所有控制參數(shù)、收集算法、STW、對象分配規(guī)則、回收策略等都與serial完全一樣(也是VM啟用CMS收集器-XX: +UseConcMarkSweepGC的默認新生代收集器).
gc.parnew.png
由于存在線程切換的開銷, ParNew在單CPU的環(huán)境中比不上Serial, 且在通過超線程技術實現(xiàn)的兩個CPU的環(huán)境中也不能100%保證能超越Serial. 但隨著可用的CPU數(shù)量的增加, 收集效率肯定也會大大增加(ParNew收集線程數(shù)與CPU的數(shù)量相同, 因此在CPU數(shù)量過大的環(huán)境中, 可用-XX:ParallelGCThreads參數(shù)控制GC線程數(shù)).
3 Parallel Scavenge收集器
與ParNew類似,Parallel Scavenge也是使用復制算法,也是并行多線程收集器。 但與其他收集器關注盡可能縮短垃圾收集時間不同, Parallel Scavenge更關注系統(tǒng)吞吐量。
系統(tǒng)吞吐量=運行用戶代碼時間 /(運行用戶代碼時間+垃圾收集時間)
停頓時間越短就越適用于用戶交互的程序--良好的響應速度能提升用戶的體驗;而高吞吐量則適用于后臺運算而不需要太多交互的任務--可以最高效率地利用CPU時間,盡快地完成程序的運算任務。
老年代
1 Serial Old收集器
Serial old是serial收集器的老年代版本, 同樣是單線程收集器,使用“標記-整理”算法。
gc.serial.old.png
2 Parallel Old收集器
Parallel old是Parallel Scavenge收老年代版本, 使用多線程和“標記-整理”算法。 吞吐量優(yōu)先,主要與Parallel Scavenge配合在注重吞吐量 及 CPU資源敏感系統(tǒng)內(nèi)使用。
gc.parnew.old.png
3 CMS收集器
CMS(Concurrent Mark Sweep)收集器是真正意義上的并發(fā)收集器,雖然已經(jīng)有了理論上表現(xiàn)更好的G1收集器。
CMS以獲取最短回收停頓時間為目標的收集器,基于”標記-清除”算法實現(xiàn),整個GC過程分為以下4個步驟:
初始標記
并發(fā)標記(GC Roots Tracing過程)
重新標記
并發(fā)清除(CMS concurrent sweep: 已死象會就地釋放,注意: 此處沒有壓縮)
其中初始標記和重新標記仍需STW。但初始標記僅只標記一下GC Roots能直接關聯(lián)到的對象, 速度很快。重新標記是為了修正并發(fā)標記期間因用戶程序繼續(xù)運行而導致標記產(chǎn)生變動的那一部分對象的標記記錄,雖然一般比初始標記階段稍長,但要遠小于并發(fā)標記時間。
gc.cms.png
(由于整個GC過程耗時最長的并發(fā)標記和并發(fā)清除階段的GC線程可與用戶線程一起工作, 所以總體上CMS的GC過程是與用戶線程一起并發(fā)地執(zhí)行的。
由于CMS收集器將整個GC過程進行了更細粒度的劃分, 因此可以實現(xiàn)并發(fā)收集、低停頓的優(yōu)勢,但它也并非十分完美,其存在缺點及解決策略如下:
CMS默認啟動的回收線程數(shù)=(CPU數(shù)目+3)4
當CPU數(shù)>4時,GC線程最多占用不超過25%的CPU資源,但是當CPU數(shù)<=4時, GC線程可能就會過多的占用用戶CPU資源, 從而導致應用程序變慢, 總吞吐量降低。
無法處理浮動垃圾, 可能出現(xiàn)Promotion Failure、Concurrent Mode Failure而導致另一次Full GC的產(chǎn)生。
浮動垃圾是指在CMS并發(fā)清理階段用戶線程運行而產(chǎn)生的新垃圾。由于在GC階段用戶線程還需運行, 因此還需要預留足夠的內(nèi)存空間給用戶線程使用,導致CMS不能像其他收集器那樣等到老年代幾乎填滿了再進行收集。 因此CMS提供了-XX:CMSInitiatingOccupancyFraction參數(shù)來設置GC的觸發(fā)百分比(以及-XX:+UseCMSInitiatingOccupancyOnly來啟用該觸發(fā)百分比),當老年代的使用空間超過該比例后CMS就會被觸發(fā)(JDK 1.6之后默認92%)。 但當CMS運行期間預留的內(nèi)存無法滿足程序需要,就會出現(xiàn)Promotion Failure等失敗, 這時VM將啟動后備預案:臨時啟用serial old收集器來重新執(zhí)行full GC(CMS通常配合大內(nèi)存使用,一旦大內(nèi)存轉(zhuǎn)入串行的serial GC, 那停頓的時間就是大家都不愿看到的了)。
最后,由于CMS采用”標記-清除”算法實現(xiàn), 可能會產(chǎn)生大量內(nèi)存碎片。內(nèi)存碎片過多可能會導致無法分配大對象而提前觸發(fā)Full GC。因此CMS提供了-XX:+UseCMSCompactAtFullCollection參數(shù),用于在Full GC后再執(zhí)行一個碎片整理過程。 但內(nèi)存整理是無法并發(fā)的,內(nèi)存碎片問題雖然沒有了,但停頓時間也因此變長了,因此CMS還提供了另外一個參數(shù)-XX:CMSFullGCsBeforeCompaction用于設置在執(zhí)行N次不進行內(nèi)存整理的Full GC后,跟著來一次帶整理的(默認為0: 每次進入Full GC時都進行碎片整理)。
4 G1收集器
HotSpot開發(fā)團隊賦予它的使命是未來可以替換掉JDK1.5中發(fā)布的CMS收集器。與CMS收集器相比G1收集器有以下特點:
空間整合,G1收集器采用標記-整理算法,不產(chǎn)生內(nèi)存空間碎片。分配大對象時不會因為無法找到連續(xù)空間而提前觸發(fā)下一次GC。
可預測停頓,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經(jīng)是實時Java(RTSJ)的垃圾收集器的特征了。
G1將Java堆劃分為多個大小相等的獨立區(qū)域(region),雖然還保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔離的了, 它們都是一部分region(不需要連續(xù))的集合。
gc.g1.png
每塊區(qū)域既有可能屬于O區(qū)、也有可能是Y區(qū), 因此不需要一次就對整個老年代/新生代回收。而是當線程并發(fā)尋找可回收的對象時, 有些區(qū)塊包含可回收的對象要比其他區(qū)塊多很多。 雖然在清理這些區(qū)塊時G1仍然需要暫停應用線程, 但可以用相對較少的時間優(yōu)先回收垃圾較多的regiorn(這也是G1命名的來源)。 這種方式保證了G1可以在有限的時間內(nèi)獲取盡可能高的收集效率。
新生代收集
gc.g1.young01.png
G1的新生代收集跟ParNew類似: 存活的對象被轉(zhuǎn)移到一個/多個Survivor Regions. 如果存活時間達到閥值, 這部分對象就會被提升到老年代。
G1的新生代收集特點如下:
一整塊堆內(nèi)存被分為多個Regions。
存活對象被拷貝到新的Survivor區(qū)或老年代。
年輕代內(nèi)存由一組不連續(xù)的heap區(qū)組成,這種方法使得可以動態(tài)調(diào)整各代區(qū)域尺寸。
Young GCs會有STW事件, 進行時所有應用程序線程都會被暫停,
多線程并發(fā)GC。
老年代收集
G1老年代GC會執(zhí)行以下階段:
注: 以下有些階段也是年輕代垃圾收集的一部分。
index
Phase
Description
(1)
初始標記 (Initial Mark: Stop the World )
在G1中, 該操作附著一次年輕代GC, 以標記Survivor中有可能引用到老年代對象的Regions。
(2)
掃描根區(qū)域 (Root Region Scanning: 與應用程序并發(fā)執(zhí)行)
掃描Survivor中能夠引用到老年代的references. 但必須在Minor GC觸發(fā)前執(zhí)行完。
(3)
并發(fā)標記 (Concurrent Marking : 與應用程序并發(fā)執(zhí)行)
在整個堆中查找存活對象, 但該階段可能會被Minor GC中斷。
(4)
重新標記 (Remark : Stop the World )
完成堆內(nèi)存中存活對象的標記. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空region直接被移除并回收,并計算所有區(qū)域的活躍度)。
(5)
清理 (Cleanup : Stop the World and Concurrent)
見下 5-1、2、3
5-1 (Stop the world)
在含有存活對象和完全空閑的區(qū)域上進行統(tǒng)計。
5-2 (Stop the world)
擦除Remembered Sets。
5-3 (Concurrent)
重置空regions并將他們返還給空閑列表(free list)。
(*)
Copying/Cleanup (Stop the World )
選擇”活躍度”最低的區(qū)域(這些區(qū)域可以最快的完成回收)??截?轉(zhuǎn)移存活的對象到新的尚未使用的regions。 該階段會被記錄到gc-log(只發(fā)生年輕代[GC pause (young)], 與老年代一起執(zhí)行則被記錄為[GC Pause (mixed)]。
G1老年代GC特點如下:
并發(fā)標記階段(index 3)
在與應用程序并發(fā)執(zhí)行的過程中會計算活躍度信息。
這些活躍度信息標識出那些regions最適合在STW期間回收(which regions will be best to reclaim during an evacuation pause)。
不像CMS有清理階段。
再次標記階段(index 4)
使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多。
空region直接被回收。
拷貝/清理階段(Copying/Cleanup Phase)
年輕代與老年代同時回收。
老年代內(nèi)存回收會基于他的活躍度信息。
補充: 關于Remembered Set
G1收集器中, region之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用都是使用remembered set來避免掃描全堆。G1中每個Region都有一個與之對應的Remembered Set,VM發(fā)現(xiàn)程序?qū)eference類型數(shù)據(jù)進行寫操作時,會產(chǎn)生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處于不同的Region中(在分代例子中就是檢查是否老年代中的對象引用了新生代的對象), 如果是, 便通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當內(nèi)存回收時,在GC根節(jié)點的枚舉范圍加入Remembered Set即可保證不對全局堆掃描也不會有遺漏。
常用的收集器組合
gc.allCollctor.jpeg
建議:如果系統(tǒng)花費很多的時間收集垃圾,請減小堆大小。full gc不應該超過 3-5 秒.
thread不要設的太高,一般一個cpu200個,多了的話,線程切換反而導致性能下降。
參考
總結
以上是生活随笔為你收集整理的java 垃圾回收 新生代_Java垃圾回收的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python围棋程序在屏幕上找棋盘_用C
- 下一篇: 同一款产品内墙第一遍刮耐水腻子二遍刮普通