java垃圾回收機制

 

1.JVM的gc概述

  gc即垃圾收集機制是指jvm用於釋放那些不再使用的對象所佔用的內存。java語言並不要求jvm有gc,也沒有規定gc如何工作。不過常用的jvm都有gc,而且大多數gc都使用類似的算法管理內存和執行收集操作。

  在充分理解了垃圾收集算法和執行過程後,纔能有效的優化它的性能。有些垃圾收集專用於特殊的應用程序。比如,實時應用程序主要是爲了避免垃圾收集中斷,而大多數OLTP應用程序則注重整體效率。理解了應用程序的工作負荷和jvm支持的垃圾收集算法,便可以進行優化配置垃圾收集器。

  垃圾收集的目的在於清除不再使用的對象。gc通過確定對象是否被活動對象引用來確定是否收集該對象。gc首先要判斷該對象是否是時候可以收集。兩種常用的方法是引用計數和對象引用遍歷。

  1.1.引用計數

  引用計數存儲對特定對象的所有引用數,也就是說,當應用程序創建引用以及引用超出範圍時,jvm必須適當增減引用數。當某對象的引用數爲0時,便可以進行垃圾收集。

  1.2.對象引用遍歷

  早期的jvm使用引用計數,現在大多數jvm採用對象引用遍歷。對象引用遍歷從一組對象開始,沿着整個對象圖上的每條鏈接,遞歸確定可到達(reachable)的對象。如果某對象不能從這些根對象的一個(至少一個)到達,則將它作爲垃圾收集。在對象遍歷階段,gc必須記住哪些對象可以到達,以便刪除不可到達的對象,這稱爲標記(marking)對象。

  下一步,gc要刪除不可到達的對象。刪除時,有些gc只是簡單的掃描堆棧,刪除未標記的對象,並釋放它們的內存以生成新的對象,這叫做清除(sweeping)。這種方法的問題在於內存會分成好多小段,而它們不足以用於新的對象,但是組合起來卻很大。因此,許多gc可以重新組織內存中的對象,並進行壓縮(compact),形成可利用的空間。

  爲此,gc需要停止其他的活動活動。這種方法意味着所有與應用程序相關的工作停止,只有gc運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的 gc不斷增加或同時運行以減少或者清除應用程序的中斷。有的gc使用單線程完成這項工作,有的則採用多線程以增加效率。

2.幾種垃圾回收機制

  2.1.標記-清除收集器

  這種收集器首先遍歷對象圖並標記可到達的對象,然後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器一般使用單線程工作並停止其他操作。

  2.2.標記-壓縮收集器

  有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象複製到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。

  2.3.複製收集器

  這種收集器將堆棧分爲兩個域,常稱爲半空間。每次僅使用一半的空間,jvm生成的新對象則放在其中的一半空間中。gc運行時,它把可到達對象複製到另一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續複製長生存期的對象則導致效率降低。

  2.4.增量收集器

  增量收集器把堆棧分爲多個域,每次僅從一個域收集垃圾。這會造成較小的應用程序中斷。

  2.5.分代收集器

  這種收集器把堆棧分爲兩個或多個域,用以存放不同壽命的對象。jvm生成的新對象一般放在其中的某個域中。過一段時間,繼續存在的對象將獲得使用期並轉入更長壽命的域中。分代收集器對不同的域使用不同的算法以優化性能。

  2.6.併發收集器

  併發收集器與應用程序同時運行。這些收集器在某點上(比如壓縮時)一般都不得不停止其他操作以完成特定的任務,但是因爲其他應用程序可進行其他的後臺操作,所以中斷其他處理的實際時間大大降低。

  2.7.並行收集器

  並行收集器使用某種傳統的算法並使用多線程並行的執行它們的工作。在多cpu機器上使用多線程技術可以顯著的提高java應用程序的可擴展性。
   注意:在Java中垃圾收集是不能被強迫立即執行的。調用System.gc()或Runtime.getRuntime().gc()靜態方法不能保證垃圾收集器的立即執行,因爲,也許存在着更高優先級的線程。
  
    詳解finalize函數

  finalize—方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的。
   finalize是位於Object類的一個方法,該方法的訪問修飾符爲protected,由於所有類爲Object的子類,因此用戶類很容易訪問到這個方法。由於,finalize函數沒有自動實現鏈式調用,我們必須手動的實現,因此finalize函數的最後一個語句通常是super.finalize()。通過這種方式,我們可以實現從下到上實現finalize的調用,即先釋放自己的資源,然後再釋放父類的資源。

  根據Java語言規範,JVM保證調用finalize函數之前,這個對象是不可達的,但是JVM不保證這個函數一定會被調用。另外,規範還保證finalize函數最多運行一次。

  很多Java初學者會認爲這個方法類似與C++中的析構函數,將很多對象、資源的釋放都放在這一函數裏面。其實,這不是一種很好的方式。原因有三,其一,GC爲了能夠支持finalize函數,要對覆蓋這個函數的對象作很多附加的工作。其二,在finalize運行完成之後,該對象可能變成可達的,GC還要再檢查一次該對象是否是可達的。因此,使用finalize會降低GC的運行性能。其三,由於GC調用finalize的時間是不確定的,因此通過這種方式釋放資源也是不確定的。

  通常,finalize用於一些不容易控制、並且非常重要資源的釋放,例如一些I/O的操作,數據的連接。這些資源的釋放對整個應用程序是非常關鍵的。在這種情況下,程序員應該以通過程序本身管理(包括釋放)這些資源爲主,以finalize函數釋放資源方式爲輔,形成一種雙保險的管理機制,而不應該僅僅依靠finalize來釋放資源。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章