【java】垃圾回收機制

Java語言規範沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾收集算法一般要做2件基本的事情:(1)發現無用信息對象;(2)回收被無用對象佔用的內存空間,使該空間可被程序再次使用。

大多數垃圾回收算法使用了根集(root set)這個概念;所謂根集就量正在執行的Java程序可以訪問的引用變量的集合(包括局部變量、參數、類變量),程序可以使用引用變量訪問對象的屬性和調用對象的方法。垃圾收集首選需要確定從根開始哪些是可達的和哪些是不可達的,從根集可達的對象都是活動對象,它們不能作爲垃圾被回收,這也包括從根集間接可達的對象。而根集通過任意路徑不可達的對象符合垃圾收集的條件,應該被回收。下面介紹幾個常用的算法。

 

  1、 引用計數法(Reference Counting Collector)

  引用計數法是唯一一種沒有使用根集的垃圾回收算法,該算法使用引用計數器來區分存活對象和不再使用的對象。一般來說,堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變量時,引用計數器置爲1。當對象被賦給任意變量時,引用計數器每次加1。當對象出了作用域後(該對象丟棄不再使用),引用計數器減1,一旦引用計數器爲0,對象就滿足了垃圾收集的條件。 
  基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜必須實時運行的程序。但引用計數器增加了程序執行的開銷,因爲每次對象賦給新的變量,計數器加1,而每次現有對象出了作用域,計數器減1。 
  方法上,有兩個重點:
  1.在“幕後”操作地址分配、提供指針操作;
  2.管理一個或多個列表,以維護在程序運行時分配的對象內存。
  在實現上,有三個重點:
  1.提供“幕後”操作的封裝;
  2.所維護的對象分配列表的信息;
  3.便於進行查找的迭代器。
  先看看方法,簡單的講就是:
  通過對每個作用域的每種對象建立一個列表,以維護動態分配的每種對象的信息。該信息包含了該對象的地址,以及該地址被多少指針所引用(引用計數)。當一個新對象被成功分配了內存以後,同時就會在該列表中註冊,並設引用計數爲1;每當有其它指針指向該對象時,列表中更新該對象地址的引用計數加1.一個簡單的策略是,進行垃圾回收的時間設在當對象操出作用範圍。需要調用析構函數時,該對象地址引用計數減1;如果對象地址的引用計數減爲0,則表示可以進行回收了。
  在實現上:
  C++中,通過重載操作符,以封裝關於對象內存的分配,對象指針的操作。達到的效果是在語法上基本上是完全按C++語法編寫程序。將需要保存在“垃圾列表”中的信息封裝成一個類,以對象實例形式放於list中,便於管理。提供一個迭代器——當然,這不是必須的,以方便遍歷維護的列表。


  2、tracing算法(Tracing Collector)

  tracing算法是爲了解決引用計數法的問題而提出,它使用了根集的概念。基於tracing算法的垃圾收集器從根集開始掃描,識別出哪些對象可達,哪些對象不可達,並用某種方式標記可達對象,例如對每個可達對象設置一個或多個位。在掃描識別過程中,基於tracing算法的垃圾收集也稱爲標記和清除(mark-and-sweep)垃圾收集器.

  3、compacting算法(Compacting Collector)

  爲了解決堆碎片問題,基於tracing的垃圾回收吸收了Compacting算法的思想,在清除的過程中,算法將所有的對象移到堆的一端,堆的另一端就變成了一個相鄰的空閒內存區,收集器會對它移動的所有對象的所有引用進行更新,使得這些引用在新的位置能識別原來的對象。在基於Compacting算法的收集器的實現中,一般增加句柄和句柄表。

     4、copying算法(Coping Collector)

   該算法的提出是爲了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分成 一個對象 面和多個空閒面,程序從對象面爲對象分配空間,當對象滿了,基於coping算法的垃圾 收集就從根集中掃描活動對象,並將每個活動對象複製到空閒面(使得活動對象所佔的內存之間沒有空閒洞),這樣空閒面變成了對象面,原來的對象面變成了空閒面,程序會在新的對象面中分配內存。

   一種典型的基於coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閒區域面,在對象面與空閒區域面的切換過程中,程序暫停執行。

   5、generation算法(Generational Collector)

   stop-and-copy垃圾收集器的一個缺陷是收集器必須複製所有的活動對象,這增加了程序等待時間,這是coping算法低效的原因。在程序設計中有這樣的規律:多數對象存在的時間比較短,少數的存在時間比較長。因此,generation算法將堆分成兩個或多個,每個子堆作爲對象的一代 (generation)。由於多數對象存在的時間比較短,隨着程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運行後,上次運行存活下來的對象移到下一最高代的子堆中,由於老一代的子堆不會經常被回收,因而節省了時間。

   6、adaptive算法(Adaptive Collector)

   在特定的情況下,一些垃圾收集算法會優於其它算法。基於Adaptive算法的垃圾收集器就是監控當前堆的使用情況,並將選擇適當算法的垃圾收集器。

 

 

 

 

 

 

 

 

 

 

 

【垃圾回收原理】

垃圾回收器是如何工作的?我現在就簡單的介紹一下

首先要明確幾點:

Java是在堆上爲對象分配空間的

垃圾回收器只跟內存有關,什麼IO啊,網絡連接啊,管它P事

當可用內存數量較低時,Sun版本的垃圾回收器纔會被激活

在垃圾回收器回收垃圾之前,我們先來了解一下Java分配對象的方式,Java的堆更像一個傳送帶,每分配一個新對象,它就往前移動一格。這意味着對象存儲空間的分配速度相當快。Java的“堆指針”只是簡單地移動到尚未分配的領域。也就是說,分配空間的時候,“堆指針”只管依次往前移動而不管後面的對象是否還要被釋放掉。如果可用內存耗盡之前程序就退出就再好不過了,這樣的話垃圾回收器壓根就不會被激活。

但是由於“堆指針”只管依次往前移動,那麼你肯定會想,總有一天內存會被耗盡,垃圾回收器就開始釋放內存。這裏有人肯定會問:怎麼判斷某個對象該被回收呢?答案就是當堆棧或靜態存儲區沒有對這個對象的引用時,就表示程序(員)對這個對象沒有興趣了,它就應該被回收了。有兩種方法來知道這個對象有沒有被引用:第一種是遍歷堆上的對象找引用;第二種是遍歷堆棧或靜態存儲區的引用找對象。前者的實現叫做“引用計數法”,意思就是當有引用連接至對象時,引用計數加1,當引用離開作用域或被置爲null時,引用計數減1,這種方法有個缺陷,如果對象之間存在循環引用,可能會出現“對象應該被回收,但引用計數卻不爲零”的情況。

Java採用的是後者,在這種方式下,Java虛擬機採用一種“自適應”的垃圾回收技術,如何處理找到的存活對象(也就是說不是垃圾),Java有兩種方式:

一種是“停止-複製”:理論上是先暫停程序的運行(所以它不屬於後臺回收模式),然後將所有存活的對象從當前堆複製到另一個堆,沒有被複制的全是垃圾。當對象被複制到新堆上時,它們是一個挨着一個的,所以新堆保持緊湊排列(這也是爲什麼分配對象的時候“堆指針”只管依次往前移動)。然後就可以按前述方法簡單、直接地分配內存了。這將導致大量內存複製行爲,內存分配是以較大的“塊”爲單位的。有了塊之後,垃圾回收器就可以不往堆裏拷貝對象了,直接就可以往廢棄的塊裏拷貝對象了。

另一種是“標記-清掃”:它的思路同樣是從堆棧和靜態存儲區出發,遍歷所有的引用,進而找出所有存活的對象。每當它找到一個存活對象,就會給對象一個標記。這個過程中不會回收任何對象。只有全部標記完成時,沒有標記的對象將被釋放,不會發生任何複製工作,所以剩下的堆空間是不連續的,然後垃圾回收器重新整理剩餘的對象,使它們是連續排列的。

當垃圾回收器第一次啓動時,它執行的是“停止-複製”,因爲這個時刻內存有太多的垃圾。然後Java虛擬機會進行監視,如果所有對象都很穩定,垃圾回收器的效率降低的話,就切換到“標記-清掃”方式;同樣,Java虛擬機會跟蹤“標記-清掃”效果,要是堆空間出現很多碎片,就會切換到“停止-複製”方式。這就是所謂的“自適應”技術。

其實仔細想一下,“停止-複製”和“標記-清掃”無非就是:“在大量的垃圾中找乾淨的東西和在大量乾淨的東西里找垃圾”。不同的環境用不同的方式,這樣做完全是爲了提高效率,要知道,無論哪種方式,Java都會先暫停程序的運行,所以,垃圾回收器的效率其實是很低的。Java用效率換回了C++沒有的垃圾回收器和運行時的靈活,我認爲這是明智的選擇(雖然它只跟內存有關),隨着硬件的飛速發展,我相信,開發時間要比運行效率重要得多!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章