JVM垃圾收集和內(nèi)存回收
一、常用的判斷對象存活算法
要進行垃圾回收,首先要做的一件事就是判斷哪些對象是垃圾,哪些對象又是可用的。下面是兩種常見的垃圾判斷算法。
- 引用計數(shù)器算法
為對象添加一個引用計數(shù)器,當有引用地方引用到它時,計算器就加一,當一個引用失效的時候,計數(shù)器就減一。
優(yōu)點:實現(xiàn)簡單,高效
缺點:很難解決對象之間循環(huán)引用的問題 - 根搜索算法
根據(jù)一系列GC ROOT的引用鏈來判斷哪些對象已經(jīng)失效。(不在引用鏈里面的對象都判為已失效)
Java中,可以作用GC ROOT的對象包括以下:
- 虛擬機棧中引用的對象(棧幀的本地變量表)。
- 方法去的靜態(tài)屬性引用的對象
- 方法區(qū)的常量引用的對象
- 本地方法棧中JNI引用的對象
二、根搜索算法
這里重點介紹一下根搜索算法,因為主流的HotSpot虛擬機和大多JVM虛擬機用的都是這一算法。
- 關(guān)于finalize()方法
被根搜索算法標記不可用的對象,其實不會馬上死亡。虛擬機會給一次自我救贖的機會。這個機會就在finalize里面。如果對象覆蓋了finalize方法,JVM第一次發(fā)現(xiàn)對象不可用的時候,會先執(zhí)行finalize方法。如果對象在finalize方法中重新與某個對象關(guān)聯(lián)起來,就可以逃過死亡。如果JVM下一次GC的時候如果發(fā)現(xiàn)該對象還是不可用,就會讓其真正死亡。
需要注意的是,每個對象只有一次救贖機會,也就是只會執(zhí)行一次ifinalize方法。也就是如果執(zhí)行完finalize方法后,該對象又被GC發(fā)現(xiàn)不可用了,這時是不會再有救贖的機會了。同時也不推薦用finalize()方法來防止對象被回收
oopMap: 讓JVM知道哪些地方存放著對象引用。
safePoint: 代碼進入安全點后才會執(zhí)行GC。Safepoint的選定既不能太少以致于讓GC等待時間太長,也不能過于頻繁以致于過分增大運行時的負荷。所以,安全點的選定基本上是以程序“是否具有讓程序長時間執(zhí)行的特征”為標準進行選定的。safePoint的選擇 - 循環(huán)的末尾 (防止大循環(huán)的時候一直不進入safepoint,而其他線程在等待它進入safepoint)
- 方法返回前
- 調(diào)用方法的call之后
- 拋出異常的位置
三、垃圾回收算法
-
標記-清除算法
先把所有不可用的內(nèi)存塊標記一下,最后統(tǒng)一清除。主要兩個缺點:
- 標記和清除的過程效率都不太高
- 會產(chǎn)生內(nèi)存碎片,等到有對象需要分配較大內(nèi)存但是又沒有這么大的連續(xù)內(nèi)存時,不得不提前觸發(fā)一次垃圾收集動作。
-
復(fù)制算法
復(fù)制算法解決了效率和內(nèi)存碎片的問題。它的理念內(nèi)存分為兩塊,內(nèi)存分配時只使用其中一塊,之后GC時將可用對象復(fù)制到另一塊內(nèi)存中,接著將原來那個內(nèi)存塊的對象全部清除掉。
這種方法簡單粗暴,效率很高。但是卻嚴重浪費了內(nèi)存空間。
現(xiàn)代商業(yè)虛擬機一般都采用復(fù)制算法來回收新生代對象。因為新生代大多都是朝生夕死,經(jīng)過一次GC后存活的對象非常少,所以可以將內(nèi)存分為8:1:1(Eden:survive:survive)。當執(zhí)行GC的時候,直接查看Eden區(qū)和其中一塊已經(jīng)分配對象的survive區(qū),然后將可用對象都復(fù)制到剩下的那塊還沒survive區(qū)中。這樣內(nèi)存使用率就高達90%了。(真正使用的內(nèi)存時8+1)。
另外,當10%的內(nèi)存空間不夠分配存活對象時,JVM會啟動擔保機制,將老生代的內(nèi)存空間預(yù)支出來使用。 -
標記-整理算法
復(fù)制算法在對象存活率很高的情況,效率會變低。所以不適合用當老生代的回收算法。于是有人提出了標記整理算法。思路就是將可用的對象都向一端移動,最后清除端邊界以外的內(nèi)存即可。 -
分代算法
現(xiàn)在JVM虛擬機一般都根據(jù)新生代和老生代的特點分別使用不同的回收算法。新生代生存率低,所以使用復(fù)制算法。老生代生存率高,所以使用標記-清除或者標記-整理算法。
四、垃圾收集器

JVM垃圾收集器.png
-
Serial 收集器
這是一款比較老的收集器。由于是單線程處理,所以GC的時候停頓明顯。jdk1.3.1這個收集器之前是唯一的選擇。
Serial對新生代對象(new)采用的是復(fù)制算法。老生代對象采用的是標記-整理算法。 -
ParNew 收集器
其實就是Serial的多線程版本。其他和Serial都差不多。
除了Serial收集器外,它只能和CMS配合使用。 -
Parallel Scavenge 收集器
采用復(fù)制算法,并行的多線程收集器。
特點就是可以人為的控制吞吐量。吞吐量就是真正代碼執(zhí)行時間和CPU消耗的總時間的比值。比如虛擬機在CPU上總共消耗了100分鐘,GC花了1分鐘。那吞吐量就是99%??梢愿鶕?jù)下面3個參數(shù)來控制吞吐量:
-XX:MaxGCPauseMillis 最大GC停頓時間(毫秒)。(也不要以為越小就越好)
-XX:GCTimeRatio 直接設(shè)置吞吐量大小,范圍0-100%
-XX:+UseAdaptiveSizePolicy 當這個參數(shù)打開之后,就不需要手工指定新生代的大?。?Xmn)、Eden與Survivor區(qū)的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節(jié)參數(shù)了,虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或者最大的吞吐量,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)。 -
Serial Old 收集器
Serial 收集器的老生代收集器。用標記-整理算法。這個收集器的主要意義是在client模式下使用。另外,它還有兩大用途。
- 與Parallel Scavenge搭配使用
- 作為CMS的后備預(yù)案,當并發(fā)收集發(fā)送 Curruent Mode Failure的時候使用。
-
Parallel Old 收集器
Parallel的老生代收集器。采用標記-整理算法。一般與Parallel Scavenge搭配使用。 -
CMS 收集器
這是一款并發(fā)低停頓收集器。它的目標就是盡可能的減少GC造成的時間停頓。所以比較適合對web應(yīng)用這種交互式應(yīng)用。
采用的是標記-清除算法。涉及的內(nèi)容有點多,就不詳細介紹。讀者可自行百度或者查看以下鏈接。
JVM實用參數(shù)(七)CMS收集器 -
G1收集器
G1收集器是收集器技術(shù)發(fā)展的最前沿結(jié)果。剛發(fā)布的JDK9已經(jīng)將它作為默認垃圾收集器。
采用標記-整理算法。深入理解JVM G1收集器