1. 程式人生 > 其它 >jvm讀書記錄3-記憶體垃圾收集演算法

jvm讀書記錄3-記憶體垃圾收集演算法

記憶體垃圾收集演算法

這裡的記憶體主要指的堆和方法區的記憶體。

一、如何判斷物件是否死亡

引用計數演算法

在物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器就減1,任何時刻計數器為零的物件就是不可能再被使用的。比較著名的應用案例有微軟com技術、python語言等,但是在Java領域,主流的Java虛擬機器裡面都沒有選用引用計數器演算法來管理記憶體,主要原因是這個看似簡單的演算法有很多例外的情況需要考慮,必須配合大量額外處理才能保證準確的工作,譬如物件之間的相互迴圈引用。

可達性分析演算法

基本思路是通過一系列成為gc roots的根物件作為起始節點集,從這些節點開始,根據引用關係向下搜尋,搜尋走過的路徑稱為“引用鏈”,如果某個物件到gc roots沒有任何引用鏈相連,或者用圖論的話來說就是從gc roots到這個物件不可達時,則證明此物件時不可能再被使用的。

在Java技術體系裡面,固定可作為gc roots的物件包括如下幾種:

1、在虛擬機器棧(棧幀中的本地變量表)中引用的物件,譬如各個執行緒被呼叫的方法堆疊中使用到的引數、區域性變數、臨時變數等。

2、方法區中類靜態屬性引用的物件,譬如Java類的引用型別靜態變數。

3、方法區中常量引用的物件,譬如字串常量池裡面的引用。

4、在本地方法棧中jni引用的物件。

5、Java虛擬機器內部的引用,如基本資料型別對應的class物件,一些常駐的異常物件,還有系統類載入器。

6、所有被同步鎖持有的物件。

7、反應Java虛擬機器內部情況的jmxbean,jvmti中註冊的毀掉、原生代碼快取。

二、引用分類

在jdk1.2版之後,Java對引用的概念進行了擴充將引用分為了4種引用,引用強度依次逐漸減弱。增加分類是為了擴充引用的語義,來描述哪些“食之無味、棄之可惜”的物件,例如在記憶體中可有可無的物件--在記憶體夠的時候就保留在虛擬機器,不夠的時候就釋放掉。

1、強引用-- 值的是程式程式碼之中普遍存在的引用賦值,即類似:object obj = new object()。無論任何情況下,只要強引用關係存在,垃圾收集器就永遠不會回收掉被引用的物件。

2、軟引用-- 描述一些還有用, 但是非必須的物件。只被軟引用關聯的物件,在系統將要發生記憶體溢位異常前,會把這些物件列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的記憶體才會丟擲異常。

3、弱引用是用來描述一些還有用,但是非必須的物件。他的強度比軟引用更弱,被弱引用關聯的物件只能生存到下一次垃圾收集發生為止。

4、虛引用也被稱為“幽靈引用”,他是最弱的一種引用關係,為一個物件設定虛引用關聯的唯一目的只是為了能在這個物件被收集器回收時收到一個系統通知。

三、物件死亡

在可達性分析演算法中判定為不可達的物件,也不是“非死不可”的,這時候他們還處於“緩刑”階段,要真正宣告一個物件死亡,至少要經歷倆次標記過程:

如果物件在進行可達性分析後發現沒有與gc roots相連線的引用鏈,那麼他將會被第一次標記,隨後進行一次篩選,篩選的條件是此物件是否有必要執行finalize方法,

假如物件沒有覆蓋這個方法或者該方法已經被呼叫過,那麼都不需要再次執行finalize方法。

如果物件需要執行finalize方法,那麼該物件會被放置在一個名為f-queue的佇列中,並在之後會建立一個執行緒去執行他們的finalize方法,但是並不一定會等待它執行結束,這樣做時為了防止阻塞死迴圈等。

四、回收方法區

方法區的垃圾收集主要回收的倆部分的內容,廢棄的常量和不再使用的型別。型別解除安裝的條件比較苛刻。

五、垃圾收集演算法

1、分代收集理論

  1. 弱分代假說:絕大多數物件都是朝生夕死的
  2. 強分代假說:熬過越多次垃圾收集過程的物件就越難以消亡。

這倆個分代假說共同奠定了常用垃圾收集器一致的設計原則:收集器應該將Java堆劃分出不同的區域,然後將回收物件依據其年齡分配到不同的區域儲存。

即朝生夕死的一個區域,難以消亡的一個區域。

關於跨代引用:跨代引用假說:跨代引用相對於同代引用來說進佔極少數。

為了解決在物件存在跨代引用時的物件掃描,只需要在新生代上建立一個全新的資料結構(記憶集),這個結構把老年代劃分成若干個小塊,標識出老年代的那一塊記憶體會存在跨代引用。

此後當發生minor gc時,豬油包含了跨代引用的小塊記憶體裡的物件才會被加入到gc roots進行掃描。

2、標記-清除演算法

演算法分為標記和清除倆個階段:首先標記出所有需要回收的物件,在標記完成之後,統一回收掉所有被標記的物件,也可以反過來,標記存活的物件,統一回收所有未被標記的物件。

缺點:1、執行效率不穩定,標記和清除的過程執行效率隨著物件資料增長而降低。2、記憶體空間碎片化的問題。

3、標記-複製演算法

一開始提出的標記-複製演算法,將可用的記憶體按容量劃分為大小相同的倆塊,每次只使用其中的一塊記憶體,當這塊記憶體快滿的時候,就將存活的物件複製到另外一塊上,

這種演算法會產大大量的記憶體記憶體複製開銷,如果多數物件上可回收的那麼該演算法只需要複製少數的物件就可以了。回收之後,也沒有空間碎片的問題,但是這種演算法的代價是將原來可用的記憶體縮小為原來的一半,空間浪費有點多。

後來andrew apple提出來一個更優化的半區複製分代策略,apple式回收的具體做法是把新生代分為一塊較大的eden空間和倆塊較小的survivor空間,每次記憶體分配只使用eden和其中一塊survivor空間,發生垃圾收集時,將eden和survivor中仍然存活的物件一次性複製到另外一塊survivor空間上,然後直接清理掉eden和已用過的那塊sruvivor空間。hotspot虛擬機器預設的eden和survivor的小大比為8:1。如果在垃圾回收的時候,一塊survivor空間不足以放下存活的物件,那麼就要依賴其他區域進行分配擔保。直接進入老年代。

4、標記-整理演算法

標記-複製演算法在物件存活率較高時就要進行較多的複製操作,效率將會降低。還有浪費一部分空間的問題,分配擔保等問題,所以一般在老年代中一般不能直接選用這種演算法。

標記整理演算法其中的標記過程和標記-清除演算法一樣,但是後續步驟不是直接對可回收的物件進行清理,而是讓所有存活的物件都想空間的一端移動。然後直接清理掉邊界為外的記憶體。

對標記整理演算法和標記清除演算法而言,是否移動物件都存在弊端,移動則記憶體回收時會更復雜,不移動則記憶體分配時會更復雜。從垃圾收集的停頓時間來看,不移動物件停頓的時間會更短,甚至可以不停頓,但是從吞吐量來看,移動物件會更划算。

hotspot虛擬機器中,關注吞吐量的parallel scavenge收集器時基於標記整理的,cms收集器時基於標記-清除演算法的,而在空間碎片較多的時候,會進行一次標記-整理。