Java垃圾收集器和內(nèi)存分配策略

JVM垃圾收集和內(nèi)存回收

一、常用的判斷對象存活算法

要進行垃圾回收,首先要做的一件事就是判斷哪些對象是垃圾,哪些對象又是可用的。下面是兩種常見的垃圾判斷算法。
  1. 引用計數(shù)器算法
    為對象添加一個引用計數(shù)器,當有引用地方引用到它時,計算器就加一,當一個引用失效的時候,計數(shù)器就減一。
    優(yōu)點:實現(xiàn)簡單,高效
    缺點:很難解決對象之間循環(huán)引用的問題
  2. 根搜索算法
    根據(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之后
  • 拋出異常的位置

三、垃圾回收算法

  1. 標記-清除算法
    先把所有不可用的內(nèi)存塊標記一下,最后統(tǒng)一清除。主要兩個缺點:
  • 標記和清除的過程效率都不太高
  • 會產(chǎn)生內(nèi)存碎片,等到有對象需要分配較大內(nèi)存但是又沒有這么大的連續(xù)內(nèi)存時,不得不提前觸發(fā)一次垃圾收集動作。
  1. 復(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ù)支出來使用。
  2. 標記-整理算法
    復(fù)制算法在對象存活率很高的情況,效率會變低。所以不適合用當老生代的回收算法。于是有人提出了標記整理算法。思路就是將可用的對象都向一端移動,最后清除端邊界以外的內(nèi)存即可。
  3. 分代算法
    現(xiàn)在JVM虛擬機一般都根據(jù)新生代和老生代的特點分別使用不同的回收算法。新生代生存率低,所以使用復(fù)制算法。老生代生存率高,所以使用標記-清除或者標記-整理算法。

四、垃圾收集器

JVM垃圾收集器.png
  1. Serial 收集器
    這是一款比較老的收集器。由于是單線程處理,所以GC的時候停頓明顯。jdk1.3.1這個收集器之前是唯一的選擇。
    Serial對新生代對象(new)采用的是復(fù)制算法。老生代對象采用的是標記-整理算法。
  2. ParNew 收集器
    其實就是Serial的多線程版本。其他和Serial都差不多。
    除了Serial收集器外,它只能和CMS配合使用。
  3. 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)。
  4. Serial Old 收集器
    Serial 收集器的老生代收集器。用標記-整理算法。這個收集器的主要意義是在client模式下使用。另外,它還有兩大用途。
  • 與Parallel Scavenge搭配使用
  • 作為CMS的后備預(yù)案,當并發(fā)收集發(fā)送 Curruent Mode Failure的時候使用。
  1. Parallel Old 收集器
    Parallel的老生代收集器。采用標記-整理算法。一般與Parallel Scavenge搭配使用。
  2. CMS 收集器
    這是一款并發(fā)低停頓收集器。它的目標就是盡可能的減少GC造成的時間停頓。所以比較適合對web應(yīng)用這種交互式應(yīng)用。
    采用的是標記-清除算法。涉及的內(nèi)容有點多,就不詳細介紹。讀者可自行百度或者查看以下鏈接。
    JVM實用參數(shù)(七)CMS收集器
  3. G1收集器
    G1收集器是收集器技術(shù)發(fā)展的最前沿結(jié)果。剛發(fā)布的JDK9已經(jīng)將它作為默認垃圾收集器。
    采用標記-整理算法。深入理解JVM G1收集器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容