首頁  >  文章  >  Java  >  Java虛擬機器中回收機制的探究

Java虛擬機器中回收機制的探究

一个新手
一个新手原創
2017-09-07 15:38:411701瀏覽


一:概述

說起垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java連結起來。在Java中,程式設計師不需要去關心記憶體動態分配和垃圾回收的問題,顧名思義,垃圾回收就是釋放垃圾佔用的空間,這一切都交給了JVM來處理。本文主要解答三個問題:

1、哪些記憶體需要回收? (哪些物件可以被看做是」垃圾「)
2、如何回收? (常用的垃圾回收演算法)
3、使用什麼工具回收? (垃圾收集器)

二、JVM垃圾判定演算法

常用的垃圾判定演算法包括:引用計數演算法,可達性分析演算法。

1、引用計數演算法

java中是透過引用來和物件進行關聯的,也就是說如果要操作對象,必須透過引用來進行。在物件中加入一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的,即表示該物件可視為」垃圾「被回收。

引用計數器演算法實現簡單,效率高;但是不能解決循環引用問問題(A 對象引用B 對象,B 對象又引用A 對象,但是A,B 對像已不被任何其他對象引用),同時每次計數器的增加和減少都帶來了許多額外的開銷,所以在JDK1.1 之後,這個演算法已經不再使用了。程式碼:

public class Main {    
    public static void main(String[] args) {
        MyTest test1 = new MyTest();
        MyTest test2 = new MyTest();

        test1.obj  = test2;
        test2.obj  = test1;//test1与test2存在相互引用 

        test1 = null;
        test2 = null;

        System.gc();//回收
    }
}

class MyTest{    
    public Object obj = null;
}

雖然最後將test1和test2賦值為null,也就是說test1和test2指向的物件已經不可能再被訪問,但是由於它們互相引用對方,導致它們的引用計數都不為0,那麼垃圾收集器就永遠不會回收它們。運行程序,從記憶體分析看到,事實上這兩個物件的記憶體被回收,這也說明了當前主流的JVM都不是採用的引用計數器演算法作為垃圾判定演算法的。

2、可達性分析演算法(根搜尋演算法)

根搜尋演算法是透過一些「GC Roots」物件作為起點,從這些節點開始往下搜索,搜尋通過的路徑成為引用鏈
(Reference Chain),當一個物件沒有被GC Roots 的引用鏈連接的時候,說明這個物件是不可用的。

Java虛擬機器中回收機制的探究

GC Roots 物件包括:
a) 虛擬機器堆疊(堆疊框架中的本機變數表)中的參考的物件。
b) 方法區域中的類別靜態屬性所引用的物件。
c) 方法區域中常數引用的物件。
d) 本地方法堆疊中JNI(即一般說的Native方法)的引用的物件。

在可達性分析演算法中,不可達的對象,也並非是「非死不可」的,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果物件在進行可達性分析後發現沒有與GC Roots相連接的參考鏈,那麼它將會被第一次標記並且進行一次篩選,篩選的條件是此物件是否需要執行finalize()方法。當物件沒有覆寫finalize()方法,或finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況都視為「不需要要執行」。注意任何物件的finalize()方法只會被系統自動執行1次。

如果這個物件被判定為需要執行finalize()方法,那麼這個物件將會放置在一個叫做F-Queue的佇列之中,並在稍後由一個由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行它。這裡所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣做的原因是,如果一個物件在finalize()方法中執行緩慢,或者發生了死循環,將會很可能會導致F-Queue佇列中其他物件永久處於等待,甚至導致整個記憶體回收系統崩潰。因此呼叫finalize()方法不代表該方法中程式碼能夠完全被執行。

finalize()方法是物件逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的物件進行第二次小規模的標記,如果物件要在finalize()中成功拯救自己-只要重新與引用鏈上的任何一個物件建立關聯即可,譬如把自己(this關鍵字)賦值給某個類別變數或物件的成員變量,那在第二次標記時它將被移除出「即將回收」的集合;如果物件這時候還沒逃脫,那基本上它就真的被回收了。從如下程式碼中我們可以看到一個物件的finalize()被執行,但是它仍然可以存活。

/**   
 * 此代码演示了两点:   
 * 1.对象可以在被GC时自我拯救。   
 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次   
 */    public class FinalizeEscapeGC {    

  public static FinalizeEscapeGC SAVE_HOOK = null;    

  public void isAlive() {    
   System.out.println("yes, i am still alive :)");    
  }    

  @Override    
  protected void finalize() throws Throwable {    
   super.finalize();    
   System.out.println("finalize mehtod executed!");    
   FinalizeEscapeGC.SAVE_HOOK = this;    
  }    

  public static void main(String[] args) throws Throwable {    
   SAVE_HOOK = new FinalizeEscapeGC();    

   //对象第一次成功拯救自己    
   SAVE_HOOK = null;    
   System.gc();    
   //因为finalize方法优先级很低,所以暂停0.5秒以等待它    
   Thread.sleep(500);    
   if (SAVE_HOOK != null) {    
    SAVE_HOOK.isAlive();    
   } else {    
    System.out.println("no, i am dead :(");    
   }    

   //下面这段代码与上面的完全相同,但是这次自救却失败了    
   SAVE_HOOK = null;    
   System.gc();    
   //因为finalize方法优先级很低,所以暂停0.5秒以等待它    
   Thread.sleep(500);    
   if (SAVE_HOOK != null) {    
    SAVE_HOOK.isAlive();    
   } else {    
    System.out.println("no, i am dead :(");    
   }    
  }    
}

運行結果:

finalize mehtod executed!    
yes, i am still alive :)    
no, i am dead :(

从运行结果可以看出,SAVE_HOOK对象的finalize()方法确实被GC收集器调用过,且在被收集前成功逃脱了。
另外一个值得注意的地方是,代码中有两段完全一样的代码片段,执行结果却是一次逃脱成功,一次失败,这是因为任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败了。

三、JVM垃圾回收算法

常用的垃圾回收算法包括:标记-清除算法,复制算法,标记-整理算法,分代收集算法

1、标记—清除算法(Mark-Sweep)(DVM 使用的算法)

标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。

Java虛擬機器中回收機制的探究

2、复制算法(Copying)

复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)。

Java虛擬機器中回收機制的探究

3、标记—整理算法(Mark-Compact)

标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。

Java虛擬機器中回收機制的探究

4、分代收集(Generational Collection)

分代收集是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

四、垃圾收集器

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。上面说过,各个平台虚拟机对内存的操作各不相同,因此本章所讲的收集器是基于JDK1.7Update14之后的HotSpot虚拟机。这个虚拟机包含的所有收集器如图:

Java虛擬機器中回收機制的探究

1、Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机
新生代收集的唯一选择。大家看名字就会知道,这个收集器是一个单线程的收集器,但它
的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,
更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

Java虛擬機器中回收機制的探究

2、ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之
外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:
PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对
象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相
当多的代码。

Java虛擬機器中回收機制的探究

3、Parallel Scavenge收集器

Java虛擬機器中回收機制的探究

Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,Parallel Scavenge收集器的目標是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用來執行使用者程式碼的時間與CPU總消耗時間的比值,也就是吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)。由於與吞吐量關係密切,Parallel Scavenge收集器也經常稱為「吞吐量優先」收集器。

4、Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同樣是一個單執行緒收集器,使用「標記-整理」演算法。這個收集器的主要意義也是在於給Client模式下的虛擬機器使用。如果在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用[1],另一種用途就是作為CMS收集器的後備預案,在並發收集發生ConcurrentMode Failure時使用。

5、Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多執行緒和「標記-整理」演算法。
這個收集器是在JDK 1.6中才開始提供的。

6、CMS收集器

Java虛擬機器中回收機制的探究

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。
運作過程分為4個步驟,包括:
a)初始標記(CMS initial mark)
b)並發標記(CMS concurrent mark)
c)重新標記(CMS remark)
d)並發清除(CMS concurrent sweep)

CMS收集器有3個缺點:
1 對CPU資源敏感。一般並發執行的程序對CPU數量都是比較敏感的
2 無法處理浮動垃圾。在並發清理階段用戶執行緒還在執行,這時產生的垃圾無法清理。
3 由於標記-清除演算法產生大量的空間碎片。

7、G1收集器

Java虛擬機器中回收機制的探究

G1是一款應用程式導向的垃圾收集器。
G1收集器的運作大致可劃分為以下步驟:

a)初始標記(Initial Marking)
b)並發標記(Concurrent Marking)
c)最終標記( Final Marking)
d)篩選回收(Live Data Counting and Evacuation)


以上是Java虛擬機器中回收機制的探究的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn