詳解java垃圾回收機制(轉)及finalize方法(轉)

詳細介紹Java垃圾回收機制html

垃圾收集GC(Garbage Collection)是Java語言的核心技術之一,以前咱們曾專門探討過Java 7新增的垃圾回收器G1的新特性,但在JVM的內部運行機制上看,Java的垃圾回收原理與機制並未改變。垃圾收集的目的在於清除再也不使用的對象。GC經過肯定對象是否被活動對象引用來肯定是否收集該對象。GC首先要判斷該對象是不是時候能夠收集。兩種經常使用的方法是引用計數和對象引用遍歷。java

引用計數收集器程序員

引用計數是垃圾收集器中的早期策略。在這種方法中,堆中每一個對象(不是引用)都有一個引用計數。當一個對象被建立時,且將該對象分配給一個變量,該變量計數設置爲1。當任何其它變量被賦值爲這個對象的引用時,計數加1(a = b,則b引用的對象+1),但當一個對象的某個引用超過了生命週期或者被設置爲一個新值時,對象的引用計數減1。任何引用計數爲0的對象能夠被看成垃圾收集。當一個對象被垃圾收集時,它引用的任何對象計數減1。web

這兒個人理解是:算法

A a=new A();   //(此時這兒的 new A(); 是對象一,a爲引用,這時間引用計數爲1 ) 編程

 A b=a; //(這時間 引用計數爲 1+1=2)瀏覽器

b=new A();//(此時這兒的 new A(); 是對象二,對象二的引用計數爲1,b爲引用,這時間引用計數爲1--以前對象一的引用計數爲 2-1=1 ) 數據結構

a=b;//(此時 對象二的引用計數爲 1+1=2,而對象一的引用技術爲 1-1=0--等待回收)多線程

上面的理解不知道對不對,如有錯誤請指正--上面加粗紅色的這句話不懂--究竟是被回收的對象的引用對象計數減一,仍是 對象的引用計數由0變爲-1併發

優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序不被長時間打斷的實時環境比較有利。

缺點: 沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.

跟蹤收集器

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

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

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

跟蹤收集器的對象引用遍歷中的對象圖,這個圖到底怎麼畫的這個不明白---我對這個的理解是相似於樹結構(甚至蜘蛛網?),可以找到的對象就在網上能夠找到,找不到的則說明沒有引用指向該對象,它在等待垃圾回收。在垃圾回收的時候,清除儲存空間,而後壓縮清除以後的空間--感受理解錯誤的的歡迎賜教

一些經常使用的垃圾收集器

(1)標記-清除收集器

這種收集器首先遍歷對象圖並標記可到達的對象,而後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器通常使用單線程工做並中止其餘操做。而且,因爲它只是清除了那些未標記的對象,而並無對標記對象進行壓縮,致使會產生大量內存碎片,從而浪費內存。

(2)標記-壓縮收集器

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

(3)複製收集器

這種收集器將堆棧分爲兩個域,常稱爲半空間。每次僅使用一半的空間,JVM生成的新對象則放在另外一半空間中。GC運行時,它把可到達對象複製到另外一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續複製長生存期的對象則致使效率下降。而且對於指定大小堆來講,須要兩倍大小的內存,由於任什麼時候候都只使用其中的一半。

 (4) 增量收集器

增量收集器把堆棧分爲多個域,每次僅從一個域收集垃圾,也可理解爲把堆棧分紅一小塊一小塊,每次僅對某一個塊進行垃圾收集。這會形成較小的應用程序中斷時間,使得用戶通常不能覺察到垃圾收集器正在工做。

(5)分代收集器

複製收集器的缺點是:每次收集時,全部的標記對象都要被拷貝,從而致使一些生命週期很長的對象被來回拷貝屢次,消耗大量的時間。而分代收集器則可解決這個問題,分代收集器把堆棧分爲兩個或多個域,用以存放不一樣壽命的對象。JVM生成的新對象通常放在其中的某個域中。過一段時間,繼續存在的對象(非短命對象)將得到使用期並轉入更長壽命的域中。分代收集器對不一樣的域使用不一樣的算法以優化性能。

並行收集器

並行收集器使用某種傳統的算法並使用多線程並行的執行它們的工做。在多CPU機器上使用多線程技術能夠顯著的提升java應用程序的可擴展性。

最後,貼出一個很是簡單的跟蹤收集器的例圖,以便你們加深對收集器的理解:

跟蹤收集器的例圖

跟蹤收集器圖例

使用垃圾收集器要注意的地方

下面將提出一些有關垃圾收集器要注意的地方,垃圾收集器知識不少,下面只列出一部分必要的知識:

(1)每一個對象只能調用finalize( )方法一次。若是在finalize( )方法執行時產生異常(exception),則該對象仍能夠被垃圾收集器收集。

(2)垃圾收集器跟蹤每個對象,收集那些不可觸及的對象(即該對象再也不被程序引用 了),回收其佔有的內存空間。但在進行垃圾收集的時候,垃圾收集器會調用該對象的finalize( )方法(若是有)。若是在finalize()方法中,又使得該對象被程序引用(俗稱復活了),則該對象就變成了可觸及的對象,暫時不會被垃圾收集了。可是因爲每一個對象只能調用一次finalize( )方法,因此每一個對象也只可能 "復活 "一次。

(3)Java語言容許程序員爲任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象以前被調用。但不要過度依賴該方法對系統資源進行回收和再利用,由於該方法調用後的執行結果是不可預知的。

(4)垃圾收集器不能夠被強制執行,但程序員能夠經過調研System.gc方法來建議執行垃圾收集。記住,只是建議。通常不建議本身寫System.gc,由於會加大垃圾收集工做量。

詳解Java GC的工做原理

概要: JVM內存結構由堆、棧、本地方法棧、方法區等部分組成,另外JVM分別對新生代和舊生代採用不一樣的垃圾回收機制。

1. 首先來看一下JVM內存結構,它是由堆、棧、本地方法棧、方法區等部分組成,結構圖以下所示。

JVM內存組成結構

1)堆

全部經過new建立的對象的內存都在堆中分配,其大小能夠經過-Xmx和-Xms來控制。堆被劃分爲新生代和舊生代,新生代又被進一步劃分爲Eden和Survivor區,最後Survivor由FromSpace和ToSpace組成,結構圖以下所示:

JVM內存結構之堆

新生代。新建的對象都是用新生代分配內存,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小能夠由-Xmn來控制,也能夠用-XX:SurvivorRatio來控制Eden和Survivor的比例舊生代。用於存放新生代中通過屢次垃圾回收仍然存活的對象

2)棧

每一個線程執行每一個方法的時候都會在棧中申請一個棧幀,每一個棧幀包括局部變量區和操做數棧,用於存放這次方法調用過程當中的臨時變量、參數和中間結果

3)本地方法棧

用於支持native方法的執行,存儲了每一個native方法調用的狀態

4)方法區

存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(PermanetGeneration)來存放方法區,可經過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。介紹完了JVM內存組成結構,下面咱們再來看一下JVM垃圾回收機制。

2. JVM垃圾回收機制

JVM分別對新生代和舊生代採用不一樣的垃圾回收機制

新生代的GC:

新生代一般存活時間較短,所以基於Copying算法來進行回收,所謂Copying算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從eden到survivor,最後到舊生代,

用javavisualVM來查看,能明顯觀察到新生代滿了後,會把對象轉移到舊生代,而後清空繼續裝載,當舊生代也滿了後,就會報outofmemory的異常,以下圖所示:

outofmemory的異常

在執行機制上JVM提供了串行GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)

1)串行GC

在整個掃描和複製過程採用單線程的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是很是高的應用上,是client級別默認的GC方式,能夠經過-XX:+UseSerialGC來強制指定

2)並行回收GC

在整個掃描和複製過程採用多線程的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別默認採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數

3)並行GC

與舊生代的併發GC配合使用

舊生代的GC:

舊生代與新生代不一樣,對象存活的時間比較長,比較穩定,所以採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,而後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之就是要減小內存碎片帶來的效率損耗。在執行機制上JVM提供了串行GC(SerialMSC)、並行GC(parallelMSC)和併發GC(CMS),具體算法細節還有待進一步深刻研究。

以上各類GC機制是須要組合使用的,指定方式由下表所示:

GC機制組合使用

finalize方法

ava提供finalize()方法,垃圾回收器準備釋放內存的時候,會先調用finalize()

          (1).對象不必定會被回收。

       (2).垃圾回收不是析構函數。

       (3).垃圾回收只與內存有關。

       (4).垃圾回收和finalize()都是靠不住的,只要JVM尚未快到耗盡內存的地步,它是不會浪費時間進行垃圾回收的。

有時當撤消一個對象時,須要完成一些操做。例如,若是一個對象正在處理的是非Java 資源,如文件句柄或window 字符字體,這時你要確認在一個對象被撤消之前要保證這些資源被釋放。爲處理這樣的情況,Java 提供了被稱爲收尾(finalization )的機制。使用該機制你能夠定義一些特殊的操做,這些操做在一個對象將要被垃圾回收程序釋放時執行。

要給一個類增長收尾(finalizer ),你只要定義finalize ( ) 方法便可。Java 回收該類的一個對象時,就會調用這個方法。在finalize ( )方法中,你要指定在一個對象被撤消前必須執行的操做。垃圾回收週期性地運行,檢查對象再也不被運行狀態引用或間接地經過其餘對象引用。就在對象被釋放以前,Java 運行系統調用該對象的finalize( ) 方法。

finalize()方法的通用格式以下:

protected void finalize( )
{
// finalization code here
}

其中,關鍵字protected是防止在該類以外定義的代碼訪問finalize()標識符。該標識符和其餘標識符將在第7章中解釋。

理解finalize( ) 正好在垃圾回收之前被調用很是重要。例如當一個對象超出了它的做用域時,finalize( ) 並不被調用。這意味着你不可能知道什麼時候——甚至是否——finalize( ) 被調用。所以,你的程序應該提供其餘的方法來釋放由對象使用的系統資源,而不能依靠finalize( ) 來完成程序的正常操做。

注意:若是你熟悉C ,那你知道C 容許你爲一個類定義一個撤消函數(destructor ),它在對象正好出做用域以前被調用。Java不支持這個想法也不提供撤消函數。finalize() 方法只和撤消函數的功能接近。當你對Java 有豐富經驗時,你將看到由於Java使用垃圾回收子系統,幾乎沒有必要使用撤消函數。

理解finalize()-析構函數的替代者

by Tim Gooch

在許多方面,Java 相似於 C++。Java 的語法很是相似於 C++,Java 有類、方法和數據成員;Java 的類有構造函數; Java 有異常處理。

可是,若是你使用過 C++ 會發現 Java 也丟掉一些多是你熟悉的特性。這些特性之一就是析構函數。取代使用析構函數,Java 支持finalize() 方法。

在本文中,咱們將描述 finalize() 與 C++ 析構函數的區別。另外,咱們將建立一個簡單的 Applet 來演示 finalize() 是如何工做的。

最終的界限

與 Java 不一樣,C++ 支持局部對象(基於棧)和全局對象(基於堆)。由於這一雙重支持,C++ 也提供了自動構造和析構,這致使了對構造函數和析構函數的調用,(對於堆對象)就是內存的分配和釋放。

在 Java 中,全部對象都駐留在堆內存,所以局部對象就不存在。結果,Java 的設計者以爲不須要析構函數(象 C++ 中所實現的)。

取而代之,Java 定義了一個特殊的方法叫作finalize() ,它提供了 C++ 析構函數的一些功能。可是,finalize() 並不徹底與 C++ 的析構函數同樣,並能夠假設它會致使一系列的問題。finalize() 方法做用的一個關鍵元素是 Java 的垃圾回收器。

垃圾回收器

在 C/C++、Pascal和其餘幾種多種用途的編程語言中,開發者有責任在內存管理上發揮積極的做用。例如,若是你爲一個對象或數據結構分配了內存,那麼當你再也不使用它時必須釋放掉該內存。

在 Java 中,當你建立一個對象時,Java 虛擬機(JVM)爲該對象分配內存、調用構造函數並開始跟蹤你使用的對象。當你中止使用一個對象(就是說,當沒有對該對象有效的引用時),JVM 經過垃圾回收器將該對象標記爲釋放狀態。

當垃圾回收器將要釋放一個對象的內存時,它調用該對象的finalize() 方法(若是該對象定義了此方法)。垃圾回收器以獨立的低優先級的方式運行,只有當其餘線程掛起等待該內存釋放的狀況出現時,它纔開始運行釋放對象的內存。(事實上,你能夠調用System.gc() 方法強制垃圾回收器來釋放這些對象的內存。)

在以上的描述中,有一些重要的事情須要注意。首先,只有當垃圾回收器釋放該對象的內存時,纔會執行finalize()。若是在 Applet 或應用程序退出以前垃圾回收器沒有釋放內存,垃圾回收器將不會調用finalize()。

其次,除非垃圾回收器認爲你的 Applet 或應用程序須要額外的內存,不然它不會試圖釋放再也不使用的對象的內存。換句話說,這是徹底可能的:一個 Applet 給少許的對象分配內存,沒有形成嚴重的內存需求,因而垃圾回收器沒有釋放這些對象的內存就退出了。

顯然,若是你爲某個對象定義了finalize() 方法,JVM 可能不會調用它,由於垃圾回收器未曾釋放過那些對象的內存。調用System.gc() 也不會起做用,由於它僅僅是給 JVM 一個建議而不是命令。

finalize() 有什麼優勢呢?

若是finalize() 不是析構函數,JVM 不必定會調用它,你可能會疑惑它是否在任何狀況下都有好處。事實上,在 Java 1.0 中它並無太多的優勢。

根據 Java 文檔,finalize() 是一個用於釋放非 Java 資源的方法。可是,JVM 有很大的可能不調用對象的finalize() 方法,所以很難證實使用該方法釋放資源是有效的。

Java 1.1 經過提供一個System.runFinalizersOnExit() 方法部分地解決了這個問題。(不要將這個方法與 Java 1.0 中的System.runFinalizations() 方法相混淆。)不象System.gc() 方法那樣,System.runFinalizersOnExit() 方法並不當即試圖啓動垃圾回收器。而是當應用程序或 Applet 退出時,它調用每一個對象的finalize() 方法。

正如你可能猜想的那樣,經過調用System.runFinalizersOnExit() 方法強制垃圾回收器清除全部獨立對象的內存,當清除代碼執行時可能會引發明顯的延遲。如今創建一個示例 Applet 來演示 Java 垃圾回收器和finalize() 方法是如何相互做用的。

回收垃圾

經過使用Java Applet Wizard 建立一個新的 Applet 開始。當提示這樣作時,輸入final_things做爲 Applet 名,並選擇不要生成源文件註釋。

接下來,在Java Applet Wizard 進行第三步,不要選擇多線程選項。在第五步以前,根據須要修改 Applet 的描述。

當你單擊Finish 後,Applet Wizard 將生成一個新的工做空間,併爲該項目建立缺省的 Java 文件。從列表 A 中選擇適當的代碼輸入(咱們已經突出顯示了你須要輸入的代碼)。

當你完成代碼的輸入後,配置Internet 瀏覽器將System.out 的輸出信息寫到Javalog.txt 文件中。(在IE 選項對話框的高級頁面中選擇起用 Java Logging。)

編譯並運行該 Applet。而後,等待 Applet 運行(你將在狀態欄中看到 Applet 已啓動的信息),退出瀏覽器,並打開Javalog.txt 文件。你將會發現相似於下列行的信息:

        1000 things constructed

        0 things finalized

正如你可以看到的那樣,創建了1,000個對象仍然沒有迫使垃圾回收器開始回收空間,即便在 Applet 退出時也沒有對象被使用。

如今,刪除在stop() 方法第一行中的註釋符以起用System.gc() 方法。再次編譯並運行該 Applet ,等待 Applet 完成運行,並退出瀏覽器。當你再次打開Javalog.txt 文件,你將看到下列行:

        1000 things constructed

        963 things finalized

此次,垃圾回收器認爲大多數對象未被使用,並將它們回收。按順序,當垃圾回收器開始釋放這些對象的內存時,JVM 調用它們的finalize() 方法。

繼承finalize()?

順便,若是你在類中定義了finalize() ,它將不會自動調用基類中的方法。在咱們討論了finalize() 與 C++ 的析構函數的不一樣點後,對這個結論不會驚訝,由於爲某個類定製的清除代碼另外一個類不必定會須要。

若是你決定要經過派生一個類的finalize() 方法來調用基類中的finalize() 方法,你能夠象其餘繼承方法同樣處理。

        protected void finalize()

        {

          super.finalize();

          // other finalization code...

        }

除了容許你控制是否執行清除操做外,這個技術還使你能夠控制當前類的finalize() 方法什麼時候執行。

結論

然而有益的是,Java 的自動垃圾回收器不會失去平衡。做爲便利的代價,你不得不放棄對系統資源釋放的控制。不象 C++ 中的析構函數,Java Applet 不會自動執行你的類中的finalize() 方法。事實上,若是你正在使用 Java 1.0,即便你試圖強制它調用finalize() 方法,也不能確保將調用它。

所以,你不該當依靠finalize() 來執行你的 Applet 和應用程序的資源清除工做。取而代之,你應當明確的清除那些資源或建立一個try...finally 塊(或相似的機制)來實現。



finalize方法是與Java編程中的垃圾回收器有關係。即:當一個對象變成一個垃圾對象的時候,若是此對象的內存被回收,那麼就能夠調用系統中定義的finalize方法來完成

固然,Java的內存回收能夠由JVM來自動完成。若是你手動使用,則可使用上面的方法。

舉例說明:

[java] view plaincopyprint?

<EMBED id=ZeroClipboardMovie_1 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=19 width=40 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=40&height=19" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. public class FinalizationDemo {  

  2.     public static void main(String[] args) {  

  3.         Cake c1 = new Cake(1);  

  4.         Cake c2 = new Cake(2);  

  5.         Cake c3 = new Cake(3);  

  6.           

  7.         c2 = c3 = null;  

  8.         System.gc(); //Invoke the Java garbage collector  

  9.     }  

  10. }  

  11.   

  12. class Cake extends Object {  

  13.     private int id;  

  14.     public Cake(int id) {  

  15.         this.id = id;  

  16.         System.out.println("Cake Object " + id + "is created");  

  17.     }  

  18.       

  19.     protected void finalize() throws java.lang.Throwable {  

  20.         super.finalize();  

  21.         System.out.println("Cake Object " + id + "is disposed");  

  22.     }  

  23. }  



結果運行:

[java] view plaincopyprint?

<EMBED id=ZeroClipboardMovie_2 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=19 width=40 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=40&height=19" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. C:\1>java FinalizationDemo  

  2. Cake Object 1is created  

  3. Cake Object 2is created  

  4. Cake Object 3is created  

  5. Cake Object 3is disposed  

  6. Cake Object 2is disposed  


final

修飾符(關鍵字)若是一個類被聲明爲final,意味着它不能再派生出新的子類,不能做爲父類被繼承。所以一個類不能既被聲明爲 abstract的,又被聲明爲final的。將變量或方法聲明爲final,能夠保證它們在使用中不被改變。被聲明爲final的變量必須在聲明時給定初值,而在之後的引用中只能讀取,不可修改。被聲明爲final的方法也一樣只能使用,不能重載。 


 

finally

異常處理時提供 finally 塊來執行任何清除操做。若是拋出一個異常,那麼相匹配的 catch 子句就會執行,而後控制就會進入 finally 塊(若是有的話)。通常異常處理塊須要。


 

finalize

方法名。Java 技術容許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,所以全部的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其餘清理工做。finalize() 方法是在垃圾收集器刪除對象以前對這個對象調用的。 

Java中全部類都從Object類中繼承finalize()方法。

當垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的finalize()方法。值得C++程序員注意的是,finalize()方法並不能等同與析構函數。Java中是沒有析構函數的。C++的析構函數是在對象消亡時運行的。因爲C++沒有垃圾回收,對象空間手動回收,因此一旦對象用不到時,程序員就應當把它delete()掉。因此析構函數中常常作一些文件保存之類的收尾工做。可是在Java中很不幸,若是內存老是充足的,那麼垃圾回收可能永遠不會進行,也就是說filalize()可能永遠不被執行,顯然期望它作收尾工做是靠不住的。

那麼finalize()到底是作什麼的呢?它最主要的用途是回收特殊渠道申請的內存。Java程序有垃圾回收器,因此通常狀況下內存問題不用程序員操心。但有一種JNI(Java Native Interface)調用non-Java程序(C或C++),finalize()的工做就是回收這部分的內存。

相關文章
相關標籤/搜索