學習java一定要知道的垃圾收集器

垃圾收集器如何演化的?
垃圾收集器的發(fā)展路線,簡單來說是隨著內(nèi)存越來越大而發(fā)生變化。
從分代算法逐漸演化為不分代算法。
從serial的幾十兆,逐漸演化到parallel的幾個G,再到CMS的幾十個G,也從此開始了并發(fā)回收。
年輕代收集器
Serial
特點:年輕代、串行回收、STW、簡單高效
Serial(串行)收集器是最基本、發(fā)展歷史最悠久的收集器,它是采用復制算法的新生代收集器,曾經(jīng)(JDK 1.3.1之前)是虛擬機新生代收集的唯一選擇。
在垃圾回收時,必須暫停其他所有線程的工作線程,即所謂的“Stop The World”。
Serial收集器是HotSpot虛擬機運行在Client模式下的默認新生代收集器。
Serial收集器具有簡單而高效,由于沒有線程交互的開銷,可以獲得最高的單線程收集效率(在單個CPU環(huán)境中)。
添加該參數(shù)來顯式的使用Serial垃圾收集器:
-XX:+UseSerialGC

ParNew
特點:年輕代、多線程回收、STW、配合CMS、核心越多效率越高
ParNew收集器是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)、收集算法、Stop The Word、對象分配規(guī)則、回收策略等都與Serial收集器一樣。
目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作。
在單CPU的環(huán)境下,其性能絕對不會有比Serial收集器有更好的效果,甚至由于多線程的切換開銷,在雙核情況下也有可能無法做到超越Serial。 在多核的情況下,GC線程數(shù)默認與核心數(shù)相同,隨著核心的增多,能獲得更好的效果。
指定使用CMS后,會默認使用ParNew作為新生代收集器:
-XX:+UseConcMarkSweepGC
強制指定使用ParNew
-XX:+UseParNewGC
指定垃圾收集的線程數(shù)量,ParNew默認開啟的收集線程與CPU的數(shù)量相同:
-XX:ParallelGCThreads

Parallel Scavenge
特點:年輕代、并行回收、吞吐量、GC自適應的調(diào)節(jié)策略(GC Ergonomics)
Parallel Scavenge收集器是一個新生代收集器,使用復制算法,且是并行的多線程收集器。
Parallel Scavenge收集器關注點是達到一個可控制的吞吐量:
(吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間))
而其他收集器關注點在盡可能的縮短垃圾收集時用戶線程的停頓時間。
控制最大垃圾收集停頓時間:參數(shù)允許的值是一個大于0的毫秒數(shù),收集器將盡可能的保證內(nèi)存垃圾回收花費的時間不超過設定的值(但是,并不是越小越好,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的,如果設置的值太小,將會導致頻繁GC,這樣雖然GC停頓時間下來了,但是吞吐量也下來了)
-XX:MaxGCPauseMillis
控制吞吐量大小:參數(shù)的值是一個大于0且小于100的整數(shù),也就是垃圾收集時間占總時間的比率,默認值是99,就是允許最大1%(即1/(1+99))的垃圾收集時間。
-XX:GCTimeRatio
GC自適應的調(diào)節(jié)策略(GC Ergonomics) :當這個參數(shù)打開之后,就不需要手工指定新生代的大小、Eden與Survivor區(qū)的比例、晉升老年代對象年齡等細節(jié)參數(shù)了,虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應的調(diào)節(jié)策略(GC Ergonomics)。只需要把基本的內(nèi)存數(shù)據(jù)設置好(如-Xmx設置最大堆),然后使用MaxGVPauseMillis參數(shù)或GCTimeRation參數(shù)給虛擬機設立一個優(yōu)化目標,JVM會自動調(diào)節(jié)其他優(yōu)化參數(shù)
-XX:+UseAdaptiveSizePolicy
老年代收集器
SerialOld
特點:老年代、串行回收、STW
Serial Old收集器是Seria收集器的老年代版本,他同樣是一個單線程收集器,使用" 標記-整理" 算法。
Serial Old收集器主要用于Client模式下的虛擬機使用。
Server模式下的兩大用途:
- 在JDK1.5及之前的版本與Parallel Scavenge收集器搭配使用;
- 作為CMS收集器的后備方案,在并發(fā)收集發(fā)生Conturrent Mode Failure時使用。
結合Serial的回收流程如下圖所示:

ParallelOld
特點:老年代、多線程、STW、吞吐量
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。此收集器的出現(xiàn)代表著“吞吐量優(yōu)先”收集器終于有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。
使用po收集參數(shù):
-XX:+UseParallelOldGC
與Parallele Scavenge結合使用的回收流程如下:

使用吞吐量收集器的吞吐量計算方式如下圖所示:

CMS(ConcurrentMarkSweep)
特點:老年代、并發(fā)的,短停頓(降低STW時間)
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它非常符合那些集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務端上的Java應用,這些應用都非常重視服務的響應速度。從名字上(“Mark Sweep”)就可以看出它是基于“標記-清除”算法實現(xiàn)的。
CMS問題比較多,所以現(xiàn)在沒有一個版本默認是CMS,只能手工指定。
CMS既然是MarkSweep,就一定會有碎片化的問題,碎片到達一定程度,CMS的老年代分配對象分配不下的時候,使用SerialOld 進行老年代回收。
CMS的回收過程主要分為4個步驟:
- 初始標記 標記一下GC Roots能直接關聯(lián)到的對象,速度很快,需要“Stop The World”。
- 并發(fā)標記 進行GC Roots Tracing(根可達算法)的過程,在整個過程中耗時最長,標記所有根節(jié)點能照的對象。與用戶線程一起工作。
- 重新標記 為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發(fā)標記的時間短。此階段也需要“Stop The World”。
- 并發(fā)清理 與用戶線程一起運行。

從以上過程中可以總結出其優(yōu)點:并發(fā)收集,并發(fā)清理,地停頓。
三個缺點:
- CMS收集器對CPU資源非常敏感。 CMS默認啟動的回收線程數(shù)是(CPU數(shù)量+3)/4,回收線程會占用部分線程資源,導致系統(tǒng)吞吐量降低。在選用CMS收集器的時候,需要考慮,當前的應用系統(tǒng),是否對CPU資源敏感。
- 垃圾收集過程中,無法處理浮動垃圾,可能會出現(xiàn)Concurrent Mode Failure問題,導致觸發(fā)Full GC。
- 浮動垃圾:是由于CMS收集器的并發(fā)清理階段,清理線程是和用戶線程一起運行,如果在清理過程中,用戶線程產(chǎn)生了垃圾對象,由于過了標記階段,所以這些垃圾對象就成為了浮動垃圾。
- CMS無法在當前垃圾收集過程中集中處理這些浮動垃圾,需要預留內(nèi)存空間去保存這些垃圾,可以通過參數(shù) -XX:CMSInitiatingOccupancyFraction 控制觸發(fā)占用老年代的百分比。
- 如果預留空間無法裝下浮動垃圾,會導致Concurrent Mode Failure失敗,這個時候,虛擬機將啟動后備預案,臨時啟動Serial Old收集器來對老年代重新進行垃圾收集,這樣會導致垃圾收集的時間邊長,特別是當老年代內(nèi)存很大的時候。所以對參數(shù)-XX:CMSInitiatingOccupancyFraction的設置,過高,會導致發(fā)生Concurrent Mode Failure,過低,則浪費內(nèi)存空間。
- 使用的"標記-清除"算法會出現(xiàn)很多內(nèi)存碎片。 過多的內(nèi)存碎片導致缺少連續(xù)的內(nèi)存空間,會影響大對象的分配,最終觸發(fā)Full GC,為了解決這個問題,CMS收集器提供了一個"-XX:+UseCMSCompactAtFullCollection"參數(shù)(默認是開啟的),用于CMS收集器在必要的時候?qū)?nèi)存碎片進行壓縮整理。由于內(nèi)存碎片整理過程不是并發(fā)的,所以會導致停頓時間變長。虛擬機還提供了一個-XX:CMSFullGCsBeforeCompaction"參數(shù),來控制進行過多少次不壓縮的Full GC以后,進行一次帶壓縮的Full GC,默認值是0,表示每次在進行Full GC前都進行碎片整理。
主要參數(shù): 使用CMS收集器:
-XX:+UseConcMarkSweepGC
Full GC后,進行一次碎片整理;整理過程是獨占的,會引起停頓時間變長:
-XX:+UseCMSCompactAtFullCollection
設置進行幾次Full GC后,進行一次碎片整理:
-XX:+CMSFullGCsBeforeCompaction
設定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量):
-XX:ParallelCMSThreads
新型收集器
G1
算法:三色標記 + SATB
G1(Garbage-First)收集器是當今收集器技術發(fā)展最前沿的成果之一,它是一款面向服務端應用的垃圾收集器,HotSpot開發(fā)團隊賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發(fā)布的CMS收集器。G1從JDK9開始已經(jīng)作為默認的垃圾回收器。如果對于應用程序來說停頓時間比吞吐量更重要,G1是非常合適的選擇。
G1與上面介紹的GC有很大的不同:
- G1的設計原則是首先收集盡可能多的垃圾(Garbage First) 。 G1并不會等內(nèi)存耗盡(串行、并行)或者快耗盡(CMS)的時候開始垃圾收集,而是在內(nèi)部采用了啟發(fā)式算法,在老年代找出具有高收集收益的分區(qū)進行收集。
- G1采用內(nèi)存分區(qū)(Region)的思路
- 邏輯上的分代概念,不是物理分代
- 所有收集都是STW,混合(mixed)收集(年輕代和老年代同時進行收集)
三色標記法 說起并發(fā)回收,就不得不了解三色標記法。先學習三色標記法,更好的理解G1的分區(qū)模型。
我們將對象分成三種類型:
- 黑色:跟對象或者該對象與它的子對象都被掃描(標記完成)。
- 灰色:對象本身被標記完成,但是還沒有掃描完該對象中的子對象
- 白色:未被掃描的對象?;蛘呤菕呙柰瓿珊?,最終白色對象為不可達對象即為垃圾對象。
其實現(xiàn)過程如下圖所示:

- 第一步:根對象被置為黑色,子節(jié)點置為灰色。
- 第二步:繼續(xù)遍歷灰色對象,將遍歷過的子節(jié)點置為灰色,當前灰節(jié)點置為黑色。
- 第三部:遍歷所有可達對象后,所有可達對象置為黑色,不可達的為白色,將要被回收。
三色標記存在的問題 如下圖所示,如果gc掃描到左側位置,這時候程序執(zhí)行C.d = D,B.d = null,則會導致B對D的引用消失,而C則引用了D,可是此時C是黑色的,不會對其子節(jié)點進行掃描,則D節(jié)點會被當做垃圾進行回收。

三色標記法問題解決方案
通常有兩種解決方案:
- 在插入的時候記錄對象(CMS增量更新)
- 在刪除的時候記錄對象(G1 STAB)
增量更新(Incremental update) : 在CMS采用的是增量更新(Incremental update),只要在寫屏障(write barrier)里發(fā)現(xiàn)要有一個白對象的引用被賦值到一個黑對象 的字段里,那就把這個白對象變成灰色的。即插入的時候記錄下來。
STAB(snapshot-at-the-beginning) : STAB 的做法在 GC 開始時,在初始標記時對內(nèi)存進行一個對象圖的邏輯快照 (snapshot),只要被快照到對象是活的,那在整個 GC 的過程中對象就被認定的是活的,即使該對象的引用稍后被修改或者刪除。
同時新分配的對象也會被認為是活的,除此之外其它不可達的對象就被認為是死掉了。這樣 STAB 就保證了真正存活的對象不會被 GC 誤回收,但同時也造成了某些可以被回收的對象逃過了 GC,導致了內(nèi)存里面存在浮動的垃圾 (float garbage),這些浮動垃圾將在下次回收時被收集。
G1的分區(qū)
G1是邏輯分代,將堆分為若干個區(qū)域(region),新生代的垃圾收集依然采用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分成很多區(qū)域,G1收集器通過將對象從一個區(qū)域復制到另外一個區(qū)域,完成了清理工作。這樣就解決了CMS中的內(nèi)存碎片問題。

如上圖分代模型所示,G1仍然有Eden,Survivor,Old等區(qū)域,同時多了一個Humongous Region(巨型對象)。
巨型對象:一個大小達到甚至超過分區(qū)大小一半的對象稱為巨型對象(Humongous Object)。
巨型對象:”會獨占一個、或多個連續(xù)分區(qū),其中第一個分區(qū)被標記為開始巨型(StartsHumongous),相鄰連續(xù)分區(qū)被標記為連續(xù)巨型(ContinuesHumongous)。巨型對象會直接在老年代分配,所占用的連續(xù)空間稱為巨型分區(qū)(Humongous Region)。G1內(nèi)部做了一個優(yōu)化,一旦發(fā)現(xiàn)沒有引用指向巨型對象,則可直接在年輕代收集周期中被回收。
G1中GC的分類:
- Young GC:主要是對Eden區(qū)進行GC,它在Eden空間耗盡時會被觸發(fā)。 在這種情況下,Eden空間的數(shù)據(jù)移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數(shù)據(jù)會直接晉升到年老代空間。Survivor區(qū)的數(shù)據(jù)移動到新的Survivor區(qū)中,也有部分數(shù)據(jù)晉升到老年代空間中。最終Eden空間的數(shù)據(jù)為空,GC停止工作,應用線程繼續(xù)執(zhí)行
- Mix GC:不僅進行正常的新生代垃圾收集,同時也回收部分后臺掃描線程標記的老年代分區(qū)。
G1是否有Full GC? 答案是肯定的,G1觸發(fā)了Full GC,這時G1會退化使用Serial Old收集器來完成垃圾的清理工作。
Full GC產(chǎn)生的原因? G1在對象復制/轉(zhuǎn)移失敗或者沒法分配足夠內(nèi)存(比如巨型對象沒有足夠的連續(xù)分區(qū)分配)時,會觸發(fā)Full GC。Full GC使用的是stop the world的單線程的Serial Old模式,所以一旦觸發(fā)Full GC則會STW應用線程,并且執(zhí)行效率很慢。JDK 8版本的G1是不提供Full GC的處理的。對于G1 GC的優(yōu)化,很大的目標就是沒有Full GC。
剩下三種GC本文會在后面主鍵補充:
- ZGC (10ms - 1ms) PK C++:算法:ColoredPointers + LoadBarrier
- Shenandoah:算法:ColoredPointers + WriteBarrier
- Eplison:Epsilon不回收內(nèi)存,只負責堆的管理與布局,對象的分配,與解釋器、編譯器、監(jiān)控子系統(tǒng)的協(xié)作; epsilon適合運行時間短、在內(nèi)存耗盡前就可退出的應用程序
到此這篇關于學習java一定要知道的垃圾收集器的文章就介紹到這了,更多相關java垃圾收集器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用springBoot中的info等級通過druid打印sql
這篇文章主要介紹了使用springBoot中的info等級通過druid打印sql,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
詳解SpringBoot 快速整合MyBatis(去XML化)
本篇文章主要介紹了詳解SpringBoot 快速整合MyBatis(去XML化),非常具有實用價值,需要的朋友可以參考下2017-10-10
SpringBoot2整合activiti6環(huán)境搭建過程解析
這篇文章主要介紹了SpringBoot2整合activiti6環(huán)境搭建過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11
Java設計模式之迭代器模式_動力節(jié)點Java學院整理
這篇文章主要介紹了Java設計模式之迭代器模式_動力節(jié)點Java學院整理,需要的朋友可以參考下2017-08-08

