歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java垃圾回收機制與引用類型

Java垃圾回收機制
JDK 5中提供了4種不同的垃圾回收機制:串行回收方式、分代回收、並行回收方式、並發標記-清除回收。

Java的垃圾回收器要負責完成3件任務:分配內存、確保被引用的對象的內存不被錯誤回收以及回收不再被引用的對象的內存空間。垃圾回收是一個復雜而且耗時的操作。如果JVM花費過多的時間在垃圾回收上,則勢必會影響應用的運行性能。一般情況下,當垃圾回收器在進行回收操作的時候,整個應用的執行是被暫時中止(stop-the-world)的。這是因為垃圾回收器需要更新應用中所有對象引用的實際內存地址。不同的硬件平台所能支持的垃圾回收方式也不同。比如在多CPU的平台上,就可以通過並行的方式來回收垃圾。而單CPU平台則只能串行進行。不同的應用所期望的垃圾回收方式也會有所不同。服務器端應用可能希望在應用的整個運行時間中,花在垃圾回收上的時間總數越小越好。而對於與用戶交互的應用來說,則可能希望所垃圾回收所帶來的應用停頓的時間間隔越小越好。對於這種情況,JVM中提供了多種垃圾回收方法以及對應的性能調優參數,應用可以根據需要來進行定制。

Java 垃圾回收機制最基本的做法是分代回收。內存中的區域被劃分成不同的世代,對象根據其存活的時間被保存在對應世代的區域中。一般的實現是劃分成3個世代:年輕、年老和永久。內存的分配是發生在年輕世代中的。當一個對象存活時間足夠長的時候,它就會被復制到年老世代中。對於不同的世代可以使用不同的垃圾回收算法。進行世代劃分的出發點是對應用中對象存活時間進行研究之後得出的統計規律。一般來說,一個應用中的大部分對象的存活時間都很短。比如局部變量的存活時間就只在方法的執行過程中。基於這一點,對於年輕世代的垃圾回收算法就可以很有針對性。

年輕世代的內存區域被進一步劃分成伊甸園(Eden)和兩個存活區(survivor space)。伊甸園是進行內存分配的地方,是一塊連續的空閒內存區域。在上面進行內存分配速度非常快,因為不需要進行可用內存塊的查找。兩個存活區中始終有一個是空白的。在進行垃圾回收的時候,伊甸園和其中一個非空存活區中還存活的對象根據其存活時間被復制到當前空白的存活區或年老世代中。經過這一次的復制之後,之前非空的存活區中包含了當前還存活的對象,而伊甸園和另一個存活區中的內容已經不再需要了,只需要簡單地把這兩個區域清空即可。下一次垃圾回收的時候,這兩個存活區的角色就發生了交換。一般來說,年輕世代區域較小,而且大部分對象都已經不再存活,因此在其中查找存活對象的效率較高。

而對於年老和永久世代的內存區域,則采用的是不同的回收算法,稱為“標記-清除-壓縮(Mark-Sweep-Compact)”。標記的過程是找出當前還存活的對象,並進行標記;清除則遍歷整個內存區域,找出其中需要進行回收的區域;而壓縮則把存活對象的內存移動到整個內存區域的一端,使得另一端是一塊連續的空閒區域,方便進行內存分配和復制。

JDK 5中提供了4種不同的垃圾回收機制。最常用的是串行回收方式,即使用單個CPU回收年輕和年老世代的內存。在回收的過程中,應用程序被暫時中止。回收方式使用的是上面提到的最基本的分代回收。串行回收方式適合於一般的單CPU桌面平台。如果是多CPU的平台,則適合的是並行回收方式。這種方式在對年輕世代進行回收的時候,會使用多個CPU來並行處理,可以提升回收的性能。並發標記-清除回收方式適合於對應用的響應時間要求比較 高的情況,即需要減少垃圾回收所帶來的應用暫時中止的時間。這種做法的優點在於可以在應用運行的同時標記存活對象與回收垃圾,而只需要暫時中止應用比較短的時間。

通過JDK中提供的JConsole可以很容易的查看當前應用的內存使用情況。在JVM啟動的時候添加參數 -verbose:gc 可以查看垃圾回收器的運行結果。

Java引用類型
如果一個內存中的對象沒有任何引用的話,就說明這個對象已經不再被使用了,從而可以成為被垃圾回收的候選。不過由於垃圾回收器的運行時間不確定,可被垃圾回收的對象的實際被回收時間是不確定的。對於一個對象來說,只要有引用的存在,它就會一直存在於內存中。如果這樣的對象越來越多,超出了JVM中的內存總數,JVM就會拋出OutOfMemory錯誤。雖然垃圾回收的具體運行是由JVM來控制的,但是開發人員仍然可以在一定程度上與垃圾回收器進行交互,其目的在於更好的幫助垃圾回收器管理好應用的內存。這種交互方式就是使用JDK 1.2引入的java.lang.ref包。

強引用
在一般的Java程序中,見到最多的就是強引用(strong reference)。如Date date = new Date(),date就是一個對象的強引用。對象的強引用可以在程序中到處傳遞。很多情況下,會同時有多個引用指向同一個對象。強引用的存在限制了對象在內存中的存活時間。假如對象A中包含了一個對象B的強引用,那麼一般情況下,對象B的存活時間就不會短於對象A。如果對象A沒有顯式的把對象B的引用設為null的話,就只有當對象A被垃圾回收之後,對象B才不再有引用指向它,才可能獲得被垃圾回收的機會。

除了強引用之外,java.lang.ref包中提供了對一個對象的不同的引用方式。JVM的垃圾回收器對於不同類型的引用有不同的處理方式。

軟引用
軟引用(soft reference)在強度上弱於強引用,通過類SoftReference來表示。它的作用是告訴垃圾回收器,程序中的哪些對象是不那麼重要,當內存不足的時候是可以被暫時回收的。當JVM中的內存不足的時候,垃圾回收器會釋放那些只被軟引用所指向的對象。如果全部釋放完這些對象之後,內存還不足,才會拋出OutOfMemory錯誤。軟引用非常適合於創建緩存。當系統內存不足的時候,緩存中的內容是可以被釋放的。比如考慮一個圖像編輯器的程序。該程序會把圖像文件的全部內容都讀取到內存中,以方便進行處理。而用戶也可以同時打開多個文件。當同時打開的文件過多的時候,就可能造成內存不足。如果使用軟引用來指向圖像文件內容的話,垃圾回收器就可以在必要的時候回收掉這些內存。

public class ImageData {
    private String path;
    private SoftReference<byte[]> dataRef;
    public ImageData(String path) {
        this.path = path;
        dataRef = new SoftReference<byte[]>(new byte[0]);
    }
    private byte[] readImage() {
        return new byte[1024 * 1024]; //省略了讀取文件的操作
  }
    public byte[] getData() {
        byte[] dataArray = dataRef.get();
        if (dataArray == null || dataArray.length == 0) {
            dataArray = readImage();
            dataRef = new SoftReference<byte[]>(dataArray);
        }
        return dataArray;
    }
}
在運行上面程序的時候,可以使用 -Xmx 參數來限制JVM可用的內存。由於軟引用所指向的對象可能被回收掉,在通過get方法來獲取軟引用所實際指向的對象的時候,總是要檢查該對象是否還存活。

Copyright © Linux教程網 All Rights Reserved