天天看點

Java教程之快速掌握Java垃圾回收機制

任何語言在運作過程中都會建立對象,也就意味着需要在記憶體中為這些對象在記憶體中配置設定空間,在這些對象失去使用的意義的時候,需要釋放掉這些内容,保證記憶體能夠提供給新的對象使用。對于對象記憶體的釋放就是垃圾回收機制,也叫做gc,對于java開發者來說gc是一個雙刃劍。

c的垃圾回收是人工的,工作量大,但是可控性高。

java是自動化的,但是可控性很差,甚至有時會出現記憶體溢出的情況,

記憶體溢出也就是jvm配置設定的記憶體中對象過多,超出了最大可配置設定記憶體的大小。

提到java的垃圾回收機制就不得不提一個方法:

System.gc()用于調用垃圾收集器,在調用時,垃圾收集器将運作以回收未使用的記憶體空間。它将嘗試釋放被丢棄對象占用的記憶體。

然而System.gc()調用附帶一個免責聲明,無法保證對垃圾收集器的調用。

是以System.gc()并不能說是完美主動進行了垃圾回收。

作為java程式員還是很有必要了解一下gc,這也是面試過程中經常出現的一道題目。

我們從三個角度來了解gc。

1jvm怎麼确定哪些對象應該進行回收

2jvm會在什麼時候進行垃圾回收的動作

3jvm到底是怎麼清楚垃圾對象的

jvm怎麼确定哪些對象應該進行回收

對象是否會被回收的兩個經典算法:引用計數法,和可達性分析算法。

引用計數法

簡單的來說就是判斷對象的引用數量。實作方式:給對象共添加一個引用計數器,每當有引用對他進行引用時,計數器的值就加1,當引用失效,也就是不在執行此對象是,他的計數器的值就減1,若某一個對象的計數器的值為0,那麼表示這個對象沒有人對他進行引用,也就是意味着是一個失效的垃圾對象,就會被gc進行回收。

但是這種簡單的算法在目前的jvm中并沒有采用,原因是他并不能解決對象之間循環引用的問題。

假設有A和B兩個對象之間互相引用,也就是說A對象中的一個屬性是B,B中的一個屬性時A,這種情況下由于他們的互相引用,進而是垃圾回收機制無法識别。

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="483" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

因為引用計數法的缺點有引入了可達性分析算法,通過判斷對象的引用鍊是否可達來決定對象是否可以被回收。可達性分析算法是從離散數學中的圖論引入的,程式把所有的引用關系看作一張圖,通過一系列的名為GC Roots的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到 GC Roots 沒有任何引用鍊相連(就是從 GC Roots 到這個對象不可達)時,則證明此對象是不可用的。

如圖:

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="275" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

二在确定了哪些對象可以被回收之後,jvm會在什麼時候進行回收

1會在cpu空閑的時候自動進行回收

2在堆記憶體存儲滿了之後

3主動調用System.gc()後嘗試進行回收

三如何回收

如何回收說的也就是垃圾收集的算法。

算法又有四個:标記-清除算法,複制算法,标記-整理算法,分代收集算法.

1 标記-清除算法。

這是最基礎的一種算法,分為兩個步驟,第一個步驟就是标記,也就是标記處所有需要回收的對象,标記完成後就進行統一的回收掉哪些帶有标記的對象。這種算法優點是簡單,缺點是效率問題,還有一個最大的缺點是空間問題,标記清除之後會産生大量不連續的記憶體碎片,當程式在以後的運作過程中需要配置設定較大對象時無法找到足夠的連續記憶體而造成記憶體空間浪費。

執行如圖:

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="269" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

2複制算法。

複制将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對其中的一塊進行記憶體回收,記憶體配置設定時也就不用考慮記憶體碎片等複雜情況。隻是這種算法的代價是将記憶體縮小為原來的一半。

複制算法的執行過程如圖:

Java教育訓練.png" width="500" height="261" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

複制收集算法在對象存活率較高時就要執行較多的複制操作,效率将會變低。更關鍵的是,浪費了一半的空間。

标記-整理算法:

标記整理算法與标記清除算法很相似,但最顯著的差別是:标記清除算法僅對不存活的對象進行處理,剩餘存活對象不做任何處理,造成記憶體碎片;而标記整理算法不僅對不存活對象進行處理清除,還對剩餘的存活對象進行整理,重新整理,是以其不會産生記憶體碎片。

标記整理算法的作用示意圖如下:

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="76" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

分代收集算法:

分代收集算法是一種比較智能的算法,也是現在jvm使用最多的一種算法,他本身其實不是一個新的算法,而是他會在具體的場景自動選擇以上三種算法進行垃圾對象回收。

那麼現在的重點就是分代收集算法中說的自動根據具體場景進行選擇。這個具體場景到底是什麼場景。

場景其實指的是針對jvm的哪一個區域,1.7之前jvm把記憶體分為三個區域:新生代,老年代,永久代。

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="434" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

了解過場景之後再結合分代收集算法得出結論:

1、在新生代中,每次垃圾收集時都發現有大批對象死去,隻有少量存活,那就選用複制算法。隻需要付出少量存活對象的複制成本就可以完成收集。

2、老年代中因為對象存活率高、沒有額外空間對他進行配置設定擔保,就必須用标記-清除或者标記-整理。

總結:

Java教程之快速掌握Java垃圾回收機制

Java教育訓練.png" width="500" height="209" style="border-width: initial; border-style: none; vertical-align: top; display: inline-block;">

注意: