1. 程式人生 > >JVM系列見解之垃圾回收概念和演算法

JVM系列見解之垃圾回收概念和演算法

1.認識垃圾回收

說到垃圾回收,首先要說明垃圾是什麼,類比於生活中垃圾,特指在記憶體中不會再被引用的物件,而回收相當於將垃圾桶“倒掉”,我們必須經常清理房間裡的垃圾。記憶體空間也是一樣,需要對一些不再使用的物件進行清理,以釋放更多空餘空間。相信學過C語言的或者c++的同學們都很清楚,我們需要手工進行垃圾回收:new關鍵字申請記憶體,delete關鍵字進行釋放記憶體。在java中,強大的jvm幫我們實現了垃圾的回收。不過作為程式設計師,我們必須瞭解jvm垃圾回收的知識,才能更好地利用jvm進行高效率的回收垃圾,從而提高程式整體的效能。

2哪些物件可以回收

2.1引用計數器法

每個物件建立時,會分配一個引用計數器。接下來每次對這個物件每次引用,對應的計數器就會加1。而每次將引用置為null,即引用失效時,計數器會執行減1。只要這個引用的計數器值為0,就可以當做是垃圾,可以被回收。
說到這裡,相信大家都覺得這個原理很容易實現。可事實上我們的jvm並不是使用這種方法判斷垃圾,這個方法實現有一個致命的問題就是:當兩個引用只是相互指向對方時,兩者的計數器值都為1,所以沒有其他任何引用能指向他們,他們依然不會被視為垃圾,這顯然是很不合理的。

2.2根搜尋

講這個之前,我先跟大家說說根物件集合由什麼組成。
根物件集合包含:

  • 常量池的靜態屬性引用物件
  • 方法區的靜態物件引用(一般指的是由fianl修飾的物件)
  • 本地方法棧引用物件
  • java棧的引用物件
  • 與一個類對應的唯一資料型別的class物件
    接下來重點來了,根搜尋演算法就是以根物件集合為起點,按照從上到下的方式搜尋被根物件集合直接或者間接鎖連線的目標物件是否可達(使用根搜尋演算法後,記憶體中存活的物件都能通過根物件集合直接或者間接找到),如果物件不可達,就意味著物件已經死亡,可將其標記為垃圾物件,可以被回收。
    這裡寫圖片描述

3 經典GC演算法

  • 引用計數法,也就是第一部分所說的引用計數器內容,這裡不再闡述
  • 標記清除法:在標記階段,首先通過根節點,標記所有能從根節點可達的物件。在清除階段,會將沒有標記的物件,視為垃圾物件,將其清理。標記清除演算法最大的問題是:會造成記憶體碎片,所以在物件對堆空間的分配時,連續的分配空間效率會高於不連續分配空間的效率。
  • 複製演算法:就是將原有的記憶體空間分為兩塊,每次只用其中一塊,在垃圾回收的時候,將還在記憶體中存活的物件複製到另外一塊還未使用的記憶體空間,之後再清除使用的那塊記憶體空間的內容,兩塊空間互換角色,完成了垃圾的回收。複製演算法的問題主要是加入存活的物件過多,複製開銷會比較大,不過複製演算法可以確保沒有記憶體碎片,一般會在新生代使用,因為新生代區可存活的物件比較少。

新生代:存放年輕物件的堆空間,指的是剛剛建立的物件或者經歷過GC的次數沒有經過jvm設定的閥值,不過這個大於這個閥值只能是一個充分非必要的條件,因為物件是否為新生代與其大小也有關係,有些大物件能直接跳過新生代進入老年代。
老年代:存放老年物件的堆空間。一般指的是經歷過多次GC後存活下來的物件。

  • 標記壓縮演算法:在標記清除演算法的基礎上做了一些優化,標記階段,也是先從根節點開始,對所有可達的節點做標記。不過在清除階段,並非只是簡單清除沒有標記的物件,而是先將可達的物件壓縮放到記憶體的一端,以此為邊界,將邊界外的記憶體空間進行清理。簡單來說標記壓縮相當於一次標記清除加上一次記憶體碎片的整理,一般在老年代使用。
  • 分代演算法:上面的所講的演算法各自都有相應的優缺點,彼此都無法代替。如何選擇合適GC演算法是一個很重要的選擇。分代演算法與其說是演算法,不如說成是一種思想。它結合了以上演算法特點以及新生代和老年代的空間物件的特點,分別採用不同的GC演算法,以提高垃圾回收的效率。
  • 分割槽演算法:將記憶體空間劃分成連續的不同小區間,每個小區間都獨立回收和控使用,最大的好處就是可以控制·一次回收多少區間垃圾。

接下來將會發表更多的JVM方面的知識,希望能和大家一起進步。如果文章中有說得不好的地方希望各位猿友指點下。 -