垃圾收集基礎(chǔ)概念

一、概述

1.為什么要了解GC

當(dāng)需要排查各種內(nèi)存溢出、內(nèi)存泄漏問題時,當(dāng)GC成為系統(tǒng)達(dá)到高并發(fā)量的瓶頸時,就需要對這些成熟的技術(shù)實施必要的監(jiān)控和調(diào)節(jié)。

2. GC了解對象

  • 主要研究:Java堆、方法區(qū)
    程序處于運行期間才知道會創(chuàng)建哪些對象,這部分的內(nèi)存分配和回收都是動態(tài)的。
  • 不需要過多考慮:程序計數(shù)器、虛擬機(jī)棧、本地方法棧
    這些區(qū)域的內(nèi)存分配和回收都具備確定性,方法結(jié)束或線程結(jié)束時內(nèi)存自然就回收了。

二、回收對象確定算法

1. 引用

JDK1.2前

引用:如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址。

缺陷:對象只存在被引用、沒有被引用兩種狀態(tài)。

JDK1.2后

對引用的概念進(jìn)行擴(kuò)充,分為了四種引用,強(qiáng)調(diào)依次減弱。

  1. 強(qiáng)引用:只要存在,垃圾收集器就不會回收掉被引用的對象。

  2. 軟引用(SoftReference):在內(nèi)存溢出之前,將這些對象列入回收范圍之中,進(jìn)行第二次回收,回收后還沒有足夠內(nèi)存,才會拋出內(nèi)存溢出異常。

  3. 弱引用(WeakReference):垃圾收集器工作時,無論內(nèi)存是否足夠,都會回收只被弱引用關(guān)聯(lián)的對象。

  4. 虛引用(PhantomReference):完全不會對其對象的生存時間構(gòu)成影響,也無法通過虛引用取得一個對象的實例。唯一目的是在這個對象被回收時收到一個系統(tǒng)通知。

2. 引用計數(shù)算法

給對象添加一個引用計數(shù)器,有一個引用它就加一,當(dāng)計數(shù)器為0時就是不可能再被使用。

優(yōu)點
  • 實現(xiàn)簡單
  • 判定效率高
缺點
  • 很難解決對象間相互循環(huán)引用的問題。

因為這一點,主流Java虛擬機(jī)沒有選用引用計數(shù)算法來管理內(nèi)存的。

3. 可達(dá)性分析算法

通過一系列GC Roots的對象作為起始點,從這些節(jié)點向下搜索,搜索走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時(GC Roots到這個對象不可達(dá)),則此對象是不可用的。

如圖object5和6,雖然6是有引用指向其的,但是因為對GC Roots不可達(dá),所以此對象不可用。

GC Roots對象
  • 虛擬機(jī)棧中引用的對象。
  • 方法區(qū)中類靜態(tài)屬性引用的對象。
  • 方法區(qū)中常量引用的對象。
  • 本地方法棧中JNI引用的對象。

4. 兩次標(biāo)記過程

宣告一個對象需要回收之前,至少經(jīng)歷兩次標(biāo)記過程。

  1. 可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連時,第一次被標(biāo)記。
  2. 篩選有必要執(zhí)行finalize()的對象。
  3. 有必要執(zhí)行的對象放入F-Queue,有虛擬機(jī)自動建立、優(yōu)先級低的線程執(zhí)行它。

雖然會執(zhí)行它,但并不一定會等待它運行結(jié)束。防止死循環(huán)導(dǎo)致F-Queue隊列處于等待,導(dǎo)致內(nèi)存回收系統(tǒng)崩潰。

  1. 看finalize()執(zhí)行情況
    • 如果在執(zhí)行finalize()中重新引用到引用鏈上,在第二次標(biāo)記時會移出回收集合。
    • 如果沒有連接上,就會被真正回收。

一個對象的finalize()方法只會被調(diào)用一次,所以一次逃脫回收后,在下一次回收時將不再會執(zhí)行finalize()。

5. 方法區(qū)的回收

方法區(qū)的回收性價比會比Java堆低很多。

主要回收目標(biāo)
  • 廢棄常量
  • 無用的類
常量的回收條件

字符串為例,如果沒有String對象引用常量池的某個常量,也沒有其他地方應(yīng)用這個常量時,就會將此常量清理出常量池。

常量池中其他類(接口)、方法、字段的符號引用也類似。

類的回收條件
  1. 該類的所有實例都被回收了。
  2. 加載該類的ClassLoader被回收了。
  3. 該類對應(yīng)的Class對象沒有被引用,無法通過反射訪問到該類。

是否對類進(jìn)行回收還需要看參數(shù),HotSpot提供了-Xnoclassgc參數(shù)控制。

在大量的使用反射、動態(tài)代理、CGLib等字節(jié)碼框架、動態(tài)生成JSP、OSGi這類頻繁定義ClassLoader的場景都需要虛擬機(jī)具備類卸載的功能,以防止方法區(qū)溢出。

三、垃圾收集算法

1. 標(biāo)記-清除算法

步驟
  1. 標(biāo)記:標(biāo)記出需要回收的對象。
  2. 清除:統(tǒng)一回收所有被標(biāo)記的對象。
問題
  1. 效率問題:標(biāo)記和清除的效率都不高。
  2. 空間碎片:標(biāo)記清除后會產(chǎn)生大量的空間碎片,可能導(dǎo)致之后無法分配連續(xù)的大空間而提前出發(fā)另一次垃圾收集動作。

2. 復(fù)制算法

步驟
  1. 將可用內(nèi)存劃分為大小相等的兩塊A、B,每次只使用其中一塊,假設(shè)使用A。
  2. 當(dāng)A內(nèi)存用完了,就將A中存活的對象復(fù)制到B塊內(nèi)存上。
  3. 將A塊內(nèi)存一次性全部清理。
  4. 再次分配時直接在B塊上偏移指針即可。

雖然解決了空間碎片的問題,但是這種算法會使可用空間大大減小,每次只可用原空間的50%。

分塊的優(yōu)化

研究新生代中98%的對象都會被回收,所以并不需要1:1分塊。可將內(nèi)存分為一塊較大的Eden,和兩塊較小的Survivor。

  1. 每次使用Eden和一塊Survivor。
  2. 回收使將其中存活對象復(fù)制到另一塊Survivor中。
  3. 最后清理掉Eden和之前的那塊Survivor內(nèi)存。


  • 優(yōu)點:HotSpot默認(rèn)Eden和Survivor比例8:1,可分配區(qū)域為原大小的90%,所以每次分配時也就只有10%的空間浪費。
  • 缺點:沒有辦法保證每次只有不多于10%大小的對象存活。當(dāng)存活對象10%時,就會導(dǎo)致Survivor空間不足,需要依賴其他內(nèi)存進(jìn)行分配擔(dān)保。

當(dāng)另一個Survivor沒有足夠空間存放上次回收的存活對象時,這些對象就會直接通過分配擔(dān)保機(jī)制進(jìn)入老年代。

復(fù)制操作在存活率較高的情況下的效率很低,并且需要進(jìn)行分配擔(dān)保。所以老年代一般不會使用復(fù)制算法。

3. 標(biāo)記-整理算法

與標(biāo)記清除算法的前面部分一樣,但是標(biāo)記整理算法不是在標(biāo)記后直接回收,而是讓所有存活對象向一端移動,然后清理掉端邊界以外的內(nèi)容。


4. 分代收集思想

根據(jù)對象存活周期的不同,將內(nèi)存劃分幾塊。一般分為新生代和老生代,根據(jù)不同的特點采用合適的算法。

新生代:復(fù)制算法。
老生代:標(biāo)記-清理或標(biāo)記-整理算法。

?著作權(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)容