天天看點

JVM筆記 | Java垃圾回收(GC)

概述
在JVM的運作時資料區中,程式計數器、JVM棧和本地方法棧随線程而生,随線程而滅,記憶體配置設定和回收具備确定性,是以這幾個區域不需要過多考慮記憶體回收問題,因為方法結束或者線程結束時,記憶體自然就跟随着回收了。而Java堆和方法區則不一樣,一個接口中的多個實作類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣,隻有在程式處于運作期才能知道會建立哪些對象,這部分記憶體的配置設定和回收都是動态的,垃圾收集器所關心的是這部分記憶體。
對象存活判定算法
在堆中存放着Java世界中幾乎所有的對象執行個體,垃圾收集器在對堆進行回收前,第一件事情就是要确定這些對象中哪些還“存活”着,那些已經“死去”(即不可能再被任何途徑使用的對象)

1. 引用的概念

在JDK1.2之後,Java對引用的概念進行了擴充,将引用分為強引用、軟引用、弱引用和虛引用四種,這四種引用強度依次減弱。
  • 強引用(Strong Reference)
    • 指建立一個對象并把這個對象指派給一個引用變量

      Object obj = new Object();

    • 隻要強引用存在,即使抛出OutOfMemoryError異常,垃圾收集器也不會回收被引用的對象。
  • 軟引用(Soft Referrence)
    • 用來描述一些還有用但非必須的對象。
    • 對于軟引用關聯的對象,在記憶體不足時會被回收。
  • 弱引用(Weak Reference)
    • 用來描述非必須對象,但強度比軟引用更弱。
    • 無論目前記憶體是否足夠,都會被回收。
  • 虛引用(Phantom Reference)
    • 不影響對象生存時間,也無法通過虛引用取得對象執行個體。
    • 存在意義在于與引用隊列關聯使用,判斷被虛引用關聯的對象是否即将被回收。

推薦閱讀:

Java的四種引用方式

2. 引用計數算法

  • 實作方式:給對象中添加一個引用計數器,每當有一個地方去引用它,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器值為0的對象就是不可能再被使用的。
  • 這種算法實作簡單、判定效率高,但是很難解決對象之間互相循環引用的問題。
  • 虛拟機不是通過引用計數算法來判斷對象是否存活。

3. 可達性分析算法

  • 實作方式:通過一系列稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain),當一個對象到GC Roots沒有任何引用鍊相連時,證明此對象是不可引用的。
    JVM筆記 | Java垃圾回收(GC)
  • 在Java中,可作為GC Roots的對象包括以下幾種:
    • JVM棧(棧幀中的本地變量表)中引用的對象;
    • 方法區中類的靜态屬性引用的對象;
    • 方法區中常量引用的對象;
    • 本地方法棧中JNI(即常說的Native方法)引用的對象。
  • 當然,可達性分析算法中不可達的對象并不是一定會被回收,如果這個對象在執行finalize()方法時,重新與引用鍊上的對象關聯起來,就會被移除出“即将回收”集合。

4. 方法區回收

  • JVM規範中說過可以不要求JVM在方法區實作垃圾回收,方法區的垃圾回收效率十分低。
  • 方法區的垃圾回收主要回收兩部分内容:
    • 廢棄常量的回收與Java堆中對象的回收十分類似,即沒有被任何其他地方引用就會被回收。
    • 滿足以下條件會被判定為無用的類
      • 該類所有的執行個體都已經被回收,即Java堆中不存在該類的任何執行個體;
      • 加載該類的 ClassLoader 已經被回收;
      • 該類對應的 java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射通路該類的方法。
垃圾收集算法

首先可以先看看下面這篇部落格,了解一下新生代和老年代的概念:

新生代和老年代

接下來介紹幾種垃圾收集算法。

1. 複制算法

  • 算法思想:将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中給一塊,當這一塊的記憶體用完了,就将還存活的對象複制到另一塊,再将已使用的一塊記憶體全部清理掉。
  • 優點:每次都對整個半區進行回收,不會産生記憶體碎片,實作簡單,運作高效。
  • 缺點:相當于将可用記憶體縮小為一半,使用率太低。
    JVM筆記 | Java垃圾回收(GC)

2. 标記—清除算法

  • 算法思想:首先标記出所有需要回收的對象,在标記完成後統一回收所有被标記的對象。
  • 不足:
    • 标記和清除兩個過程的效率都不高;
    • 标記清除後會産生大量不連續的記憶體碎片,造成後面無法為較大對象配置設定空間,頻繁觸發垃圾收集,影響系統性能。
      JVM筆記 | Java垃圾回收(GC)

3. 标記—整理算法

  • 算法思想:首先标記出所有需要回收的對象,然後将所有存活的對象移動到一端,然後直接清理端邊界以外的全部記憶體。
  • 優點:可以應對大量對象存活,隻有少量記憶體需要回收的情況,适合老年代使用。
    JVM筆記 | Java垃圾回收(GC)

4. 分代收集算法

  • 這種算法被當代虛拟機廣泛使用。
  • 算法思想:根據對象存活周期将Java堆分為新生代和老年代,分别使用合适的算法進行垃圾收集。
    • 新生代對象存活率低,使用複制算法,隻需付出少量存活對象的複制成本就可以完成收集。
    IBM公司的專門研究表明,新生代中的對象98%是“朝生夕死”,是以可以将記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor空間。回收時,将這兩塊空間中存活的對象複制到另一塊Survivor空間中,最後清理掉Eden和Survivor空間。這樣空間使用率就達到了90%。當然我們不能保證每次都隻有不多于10%的對象存活,這時就需要依賴老年代空間進行配置設定擔保,即讓survivor空間存放不下的對象通過配置設定擔保機制進入老年代。
    • 老年代對象存活率高,使用标記—清理算法或者标記—整理算法。

上一篇:

JVM筆記 | Java記憶體管理