對象回收斷定與垃圾回收算法-JVM學習筆記(1)

本章要探究的問題 :

GC在回收內存時 :java

  1. 怎麼判斷哪些內存須要回收 ?
  2. 何時回收?

在幾個線程私有的運行時區域:
算法

image.png

  • 虛擬機棧
  • 程序計數器
  • 本地方法棧

它們的內存分配和回收大多都具備肯定性,隨着線程的建立而產生,隨着線程的中止而被回收。棧幀中的內存大小基本在類的結構肯定下來時就已知。bash

而在線程共有的 Java堆(Heap)方法區(Class(Method) Area) 這兩個區域則不一樣:優化

image.png

好比,一個接口有不一樣的實現類(類的信息在方法區中),這幾個實現類的內存大小確定不一,無法在運行前就已知須要多大的內存,只有在運行期間才知道建立的對象的大小。spa


一,哪些內存須要回收?

在知道哪些內存須要回收以前,咱們要知道怎麼判斷一個對象是否還存活,當它再也不存活時,就回收它。
引用計數算法 就是用來判斷對象是否存活的一個算法。線程

1,引用計數算法(Reference Counting)

算法描述:給對象添加一個引用計數器,當有一個地方引用了它,計數器+1,當引用失效,計數器-1,在任什麼時候刻,計數器爲0時此對象將不能再被使用。code

引用計數法在大多數狀況下表現都不錯,也有被不少公司採用的應用案例。可是在JVM中並無採用這種算法,緣由是:沒法解決對象之間存在相互引用的問題cdn

public class Person {
    Object instance = null;

    public static void main(String[] args) {
        Person a = new Person();
        Person b = new Person();
        
        a.instance = b;
        b.instance = a;
        
        a = null;
        b = null;// 正常狀況下在這裏GC就會把a,b回收掉
    }
}
複製代碼

正常狀況下在執行11-12行代碼時,JVM的GC會把a,b兩個對象回收,可是在引用計數算法的狀況下:對象

  • 執行 a=null 時,a的引用計數器值爲1,由於b對象在引用它。
  • 執行 b=null 時,b的引用計數器值爲1,由於a對象在引用它。

2,可達性分析(Reachability Analysis)算法

在Java語言中是經過可達性分析來判斷對象是否存活。
算法描述 : 經過一系列的 GC Roots 做爲起始點,從這些起始點開始向下搜索,能搜索的到的對象說明其可用,不會被GC回收掉,搜索所走過的路徑稱爲 引用鏈(Reference Chain) 。相反,若是一個對象沒有到達GC Roots的路徑,則說明它不可用,被斷定爲可被GC回收的對象。blog

image.png

如圖 : 1區域的對象雖然互相關聯,可是它們不可到達GC Roots,因此他們會被回收掉,而2區域的對象與GC Roots之間是有可到達路徑的,因此它們不會被回收。

什麼是GC Roots ?

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類的靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI(Native方法)引用的對象

這些均可做爲GC Roots.

3,什麼是引用(Reference)

咱們在上面的 引用計數算法可達性分析 中,都提到了 對象之間的引用 關係。

在Java1.2以前,關於 引用 的定義 :

若是 reference 類型的數據存儲的數值表明的是另外一塊內存的起始地址,就說這塊內存表明一個引用。

JDK1,2以後,又引入了 強引用軟引用弱引用虛引用 ,這四個概念,而且這四種表現的引用關係愈來愈弱。

  • 強引用(Strong Reference) :

例:

Object o = new Object();
複製代碼


只要強引用還在,GC永遠不會回收掉被引用的對象。

  • 軟引用(Soft Reference) :

有用,但非必須,在將要發生內存溢出時,會把 軟引用 的對象回收掉,若是內存依然不夠用,則拋出OOM異常。

  • 弱引用(Weak Reference):

非必需對象,只要GC發生了垃圾回收,無論此時內存是否充足, 弱引用 的對象都會被回收掉。

  • 虛引用(Phantom Reference):
    • 最弱的引用關係
    • 沒法經過虛引用構造市實例。
    • 惟一的做用就是在虛引用關聯的對象被GC回收掉時,能夠接受到一個信號。

4,如何判斷一個對象可回收(已死)?

一個對象僅僅經過上面說的可達性分析看它沒有與GC ROOTS關聯來斷定這個對象是否可被回收是不夠的。

一個對象要通過下面一段判斷過程來判斷它是否要被回收(建議收藏(^__^) 嘻嘻……):

image.png

5,方法區的回收

上面咱們說的是存在於Java堆中的對象的回收,但其實在方法區還要回收如下東西:

① 回收廢棄常量

假如常量池中有一個字符串 "abc" ,可是系統中沒有一個String 對象指向它,也就是這個常量沒有被引用,
當GC在回收時會回收此字面量。

② 回收廢棄的類(無用的類)

  1. 該類的實例都已被回收,Java堆中不存在任何該類的實例。
  2. 加載該類的ClassLoader已經被回收。
  3. 該類的Java.lang.class對象沒有被引用(在反射中會被用到這點)。

③ 方法區的回收策略:

GC在回收方法區時會採用一下2種方式:

  • 標記-整理
  • 標記-清除


二,如何回收?

GC在回收內存時會採用多種垃圾收集算法,這些算法各有優劣。

1.標記-清除(Mark-Sweep)算法

此算法是最基礎也是最古老的垃圾回收算法,該算法主要通過2個過程

① 算法描述

  1. 標記階段:通過如何判斷一個對象可被回收所述,對可被回收的對象進行標記。
  2. 清除階段:將被標記的對象統一回收。

② 算法缺陷

  1. 效率問題:此種算法標記和清除的效率都不高。
  2. 標記清除後產生大量不連續的內存空間碎片。

2.複製(Copying)算法

複製算法針對效率問題進行了優化,它將內存區域劃分爲2塊,每次只使用其中一塊。

  • 活動區域
  • 空閒區域

① 算法描述

image.png

如圖:

  • 回收前 : 內存被劃分爲左右兩側區域,右側爲空閒區域,暫時不使用它
  • 回收時 : 將左側要被回收的部分(黑色) 回收掉,而後將4個存活對象(淡灰色)移動到右側的空閒區域,而且作了2件事
    • 將移動到空閒區域的存活對象按內存地址進行排列。
    • 將存活對象指向的舊地址指向新內存地址。
  • 回收後 : 原先的右側空閒區域變爲活動區域,左側的活動區域變爲空閒區域。

左右兩側的區域狀態在每一次回收後都來回轉換...

② 算法缺陷

  1. 很顯然,這種算法浪費了通常內存。
  2. 當活動區域的100%的對象都還在活躍,那麼在回收時須要將所有的對象複製到右側的空閒區域,此時的效率就很低。

③ 算法應用

IBM公司經研究代表,Java堆新生代種的對象98%是 '朝生夕死' 的對象,好比臨時變量等做用域不多的對象。
因此如今的虛擬機並不會按照 1:1的比例劃分兩個區域。

image.png

如今的JVM虛擬機中,將新生代劃分爲一塊 Eden 區域,和2塊較小的 Survivor 區域(from ,to區)。
每次使用Eden區和1塊Survivor區(from區)最爲活動區域,當發生內存回收時,將這2塊內存中的存活對象複製到另外一塊Survivor區(to區)。

HotSpot 虛擬機中,Eden區和Survivor的劃分是: 8: 1,這樣,活動區域佔新生代的 (8+1)/10 *100% = 90%,只有10%的內存浪費。

老年代 : 當將存活對象從活動區域(Eden,from) 複製到 to區時,若是to區不夠用,則將剩下的存活對象放到老年代。

3.標記-整理算法

標記-整理主要運用於老年代中。

① 算法描述

image.png

此算法與標記-清除算法相似,也是經歷2個階段:

  1. 標記階段:此階段於標記-清除中的標記階段相同,都是標記出要回收的對象。
  2. 整理階段:把全部存活的對像,按內存地址排列移動到內存區域的一端,將端邊界之外的區域進行回收。

② 算法應用

因爲老年代的特色,對象的存活率較高,沒有額外的空閒區域,因此 老年代適用 標記-清除和標記-整理算法。








(完~)

Reference:
深刻理解Java虛擬機

image.png
相關文章
相關標籤/搜索