1. 程式人生 > >Jvm垃圾回收器(基礎篇)

Jvm垃圾回收器(基礎篇)

一:概述

  在這篇文章中《Jvm執行時資料區》介紹了Java記憶體執行時區域的各個部分,其中程式計數器、虛擬機器棧、本地方法棧,3個區域隨著執行緒的生存而生存的。記憶體分配和回收都是確定的。隨著執行緒的結束記憶體自然就被回收了,因此不需要考慮垃圾回收的問題。而Java堆和方法區則不一樣,各執行緒共享,記憶體的分配和回收都是動態的。因此垃圾收集器所關注的都是這部分記憶體。

  接下來我們就討論Jvm是怎麼回收這部分記憶體的。在進行回收前垃圾收集器第一件事情就是確定哪些物件還存活,哪些已經死去。下面介紹兩種基礎的回收演算法。

1.1 引用計數演算法

  給物件新增一個引用計數器,每當有一個地方引用它時計數器就+1,當引用失效時計數器就-1,。只要計數器等於0的物件就是不可能再被使用的。

  此演算法在大部分情況下都是一個不錯的選擇,也有一些著名的應用案例。但是Java虛擬機器中是沒有使用的。

  優點:實現簡單、判斷效率高。

  缺點:很難解決物件之間迴圈引用的問題。例如下面這個例子

Object a = new Object(); 
Object b = new Object(); 
a=b; 
b=a; 
a=b=null; //這樣就導致gc無法回收他們。  

1.2 可達性分析演算法

  通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有使用任何引用鏈時,則說明該物件是不可用的。

  主流的商用程式語言(Java、C#等)在主流的實現中,都是通過可達性分析來判定物件是否存活的。

  通過下圖來清晰的感受gc root與物件展示的聯絡。所示灰色區域物件是存活的,Object5/6/7均是可回收的物件

  

 在Java語言中,可作為GC Roots 的物件包括下面幾種

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件
  • 方法區中靜態變數引用的物件
  • 方法區中常量引用的物件
  • 本地方法棧(即一般說的 Native 方法)中JNI引用的物件

 優點:更加精確和嚴謹,可以分析出迴圈資料結構相互引用的情況;

 缺點:實現比較複雜、需要分析大量資料,消耗大量時間、分析過程需要GC停頓(引用關係不能發生變化),即停頓所有Java執行執行緒(稱為"Stop The World",是垃圾回收重點關注的問題)。

二:引用

 在jdk1.2之後,Java對引用的概念進行了擴充,總體分為4類:強引用、軟引用、弱引用、虛引用,這4中引用強度依次逐漸減弱。

  • 強引用:指在程式碼中普遍存在的,類似 Object obj = new Object(); 這類的引用,只有強引用還存在,GC就永遠不會收集被引用的物件
  • 軟引用:指一些還有用但並非必須的物件。直到記憶體空間不夠時(丟擲OutOfMemoryError之前),才會被垃圾回收。採用SoftReference類來實現軟引用
  • 弱引用:用來描述非必須物件。當垃圾收集器工作時就會回收掉此類物件。採用WeakReference類來實現弱引用。
  • 虛引用:一個物件是否有虛引用的存在,完全不會對其生存時間構成影響, 唯一目的就是能在這個物件被回收時收到一個系統通知, 採用PhantomRenference類實現

 2.1 判斷一個物件生存還是死亡

  宣告一個物件死亡,至少要經歷兩次標記。

  1、第一次標記

  如果物件進行可達性分析演算法之後沒發現與GC Roots相連的引用鏈,那它將會第一次標記並且進行一次篩選。

  篩選條件:判斷此物件是否有必要執行finalize()方法。

  篩選結果:當物件沒有覆蓋finalize()方法、或者finalize()方法已經被JVM執行過,則判定為可回收物件。如果物件有必要執行finalize()方法,則被放入F-Queue佇列中。稍後在JVM自動建立、低優先順序的Finalizer執行緒(可能多個執行緒)中觸發這個方法;  

  2、第二次標記

  GC對F-Queue佇列中的物件進行二次標記。

  如果物件在finalize()方法中重新與引用鏈上的任何一個物件建立了關聯,那麼二次標記時則會將它移出“即將回收”集合。如果此時物件還沒成功逃脫,那麼只能被回收了。

  3、finalize() 方法

  finalize()是Object類的一個方法、一個物件的finalize()方法只會被系統自動呼叫一次,經過finalize()方法逃脫死亡的物件,第二次不會再呼叫;

  特別說明:並不提倡在程式中呼叫finalize()來進行自救。建議忘掉Java程式中該方法的存在。因為它執行的時間不確定,甚至是否被執行也不確定(Java程式的不正常退出),而且執行代價高昂,無法保證各個物件的呼叫順序(甚至有不同執行緒中呼叫)。

三:回收方法區  

  永久代的垃圾收集主要分為兩部分內容:廢棄常量和無用的類

3.1 回收廢棄常量

  回收廢棄常量與Java堆的回收類似。下面舉個栗子說明

  假如一個字串“abc” 已經進入常量池中,但當前系統沒有一個string物件是叫做abc的,也就是說,沒有任何string物件的引用指向常量池中的abc常量,也沒用其他地方引用這個字面量。如果這是發生記憶體回收,那麼這個常量abc將會被清理出常量池。常量池中的其他類(介面)、方法、欄位的符號引用也與此類似。

3.2 回收無用的類

  需要同時滿足下面3個條件的才能算是無用的類。

  1. 該類所有的例項都已經被回收,也就是Java堆中無任何改類的例項。
  2. 載入該類的ClassLoader已經被回收。
  3. 該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法

  虛擬機器可以對同時滿足這三個條件的類進行回收,但不是必須進行回收的。是否對類進行回收,HotSpot虛擬機器提供了-Xnoclassgc引數進行控制。

----對《深入理解Java虛擬機器》第3章垃圾收集器與記憶體分配策略 3.2小節總結。接下來總結3.3小結垃圾收集演算法。