久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Java垃圾回收機(jī)制與引用類型

 風(fēng)自向前 2011-02-22

轉(zhuǎn)自:http://www./cn/articles/cf-java-garbage-references


Java語(yǔ)言的一個(gè)重要特性是引入了自動(dòng)的內(nèi)存管理機(jī)制,使得開(kāi)發(fā)人員不用自己來(lái)管理應(yīng)用中的內(nèi)存,。C/C++開(kāi)發(fā)人員需要通過(guò)malloc/free 和new/delete等函數(shù)來(lái)顯式的分配和釋放內(nèi)存,。這對(duì)開(kāi)發(fā)人員提出了比較高的要求,容易造成內(nèi)存訪問(wèn)錯(cuò)誤和內(nèi)存泄露等問(wèn)題,。一個(gè)常見(jiàn)的問(wèn)題是會(huì)產(chǎn)生“懸掛引用(dangling references)”,即一個(gè)對(duì)象引用所指向的內(nèi)存區(qū)塊已經(jīng)被錯(cuò)誤的回收并重新分配給新的對(duì)象了,,程序如果繼續(xù)使用這個(gè)引用的話會(huì)造成不可預(yù)期的結(jié)果。開(kāi)發(fā)人員有可能忘記顯式的調(diào)用釋放內(nèi)存的函數(shù)而造成內(nèi)存泄露,。而自動(dòng)的內(nèi)存管理則是把管理內(nèi)存的任務(wù)交給編程語(yǔ)言的運(yùn)行環(huán)境來(lái)完成。開(kāi)發(fā)人員并不需要關(guān)心內(nèi)存的分配和回收的底層細(xì)節(jié),。Java平臺(tái)通過(guò)垃圾回收器來(lái)進(jìn)行自動(dòng)的內(nèi)存管理,。


Java的垃圾回收器要負(fù)責(zé)完成3件任務(wù):分配內(nèi)存,、確保被引用的對(duì)象的內(nèi)存不被錯(cuò)誤回收以及回收不再被引用的對(duì)象的內(nèi)存空間,。垃圾回收是一個(gè)復(fù)雜而且耗時(shí)的操作,。如果JVM花費(fèi)過(guò)多的時(shí)間在垃圾回收上,,則勢(shì)必會(huì)影響應(yīng)用的運(yùn)行性能,。一般情況下,,當(dāng)垃圾回收器在進(jìn)行回收操作的時(shí)候,,整個(gè)應(yīng)用的執(zhí)行是被暫時(shí)中止(stop-the-world)的,。這是因?yàn)槔厥掌餍枰聭?yīng)用中所有對(duì)象引用的實(shí)際內(nèi)存地址。不同的硬件平臺(tái)所能支持的垃圾回收方式也不同,。比如在多CPU的平臺(tái)上,,就可以通過(guò)并行的方式來(lái)回收垃圾,。而單CPU平臺(tái)則只能串行進(jìn)行。不同的應(yīng)用所期望的垃圾回收方式也會(huì)有所不同,。服務(wù)器端應(yīng)用可能希望在應(yīng)用的整個(gè)運(yùn)行時(shí)間中,花在垃圾回收上的時(shí)間總數(shù)越小越好,。而對(duì)于與用戶交互的應(yīng)用來(lái)說(shuō),,則可能希望所垃圾回收所帶來(lái)的應(yīng)用停頓的時(shí)間間隔越小越好,。對(duì)于這種情況,JVM中提供了多種垃圾回收方法以及對(duì)應(yīng)的性能調(diào)優(yōu)參數(shù),應(yīng)用可以根據(jù)需要來(lái)進(jìn)行定制,。
Java垃圾回收機(jī)制

Java 垃圾回收機(jī)制最基本的做法是分代回收,。內(nèi)存中的區(qū)域被劃分成不同的世代,對(duì)象根據(jù)其存活的時(shí)間被保存在對(duì)應(yīng)世代的區(qū)域中,。一般的實(shí)現(xiàn)是劃分成3個(gè)世代:年輕,、年老和永久,。內(nèi)存的分配是發(fā)生在年輕世代中的,。當(dāng)一個(gè)對(duì)象存活時(shí)間足夠長(zhǎng)的時(shí)候,,它就會(huì)被復(fù)制到年老世代中。對(duì)于不同的世代可以使用不同的垃圾回收算法,。進(jìn)行世代劃分的出發(fā)點(diǎn)是對(duì)應(yīng)用中對(duì)象存活時(shí)間進(jìn)行研究之后得出的統(tǒng)計(jì)規(guī)律,。一般來(lái)說(shuō),,一個(gè)應(yīng)用中的大部分對(duì)象的存活時(shí)間都很短。比如局部變量的存活時(shí)間就只在方法的執(zhí)行過(guò)程中,?;谶@一點(diǎn),對(duì)于年輕世代的垃圾回收算法就可以很有針對(duì)性,。

年輕世代的內(nèi)存區(qū)域被進(jìn)一步劃分成伊甸園(Eden)和兩個(gè)存活區(qū)(survivor space),。伊甸園是進(jìn)行內(nèi)存分配的地方,,是一塊連續(xù)的空閑內(nèi)存區(qū)域,。在上面進(jìn)行內(nèi)存分配速度非常快,,因?yàn)椴恍枰M(jìn)行可用內(nèi)存塊的查找。兩個(gè)存活區(qū)中始終有一個(gè)是空白的,。在進(jìn)行垃圾回收的時(shí)候,,伊甸園和其中一個(gè)非空存活區(qū)中還存活的對(duì)象根據(jù)其存活時(shí)間被復(fù)制到當(dāng)前空白的存活區(qū)或年老世代中。經(jīng)過(guò)這一次的復(fù)制之后,,之前非空的存活區(qū)中包含了當(dāng)前還存活的對(duì)象,而伊甸園和另一個(gè)存活區(qū)中的內(nèi)容已經(jīng)不再需要了,,只需要簡(jiǎn)單地把這兩個(gè)區(qū)域清空即可。下一次垃圾回收的時(shí)候,,這兩個(gè)存活區(qū)的角色就發(fā)生了交換。一般來(lái)說(shuō),,年輕世代區(qū)域較小,而且大部分對(duì)象都已經(jīng)不再存活,,因此在其中查找存活對(duì)象的效率較高,。

而對(duì)于年老和永久世代的內(nèi)存區(qū)域,則采用的是不同的回收算法,,稱為“標(biāo)記-清除-壓縮(Mark-Sweep-Compact)”,。標(biāo)記的過(guò)程是找出當(dāng)前還存活的對(duì)象,,并進(jìn)行標(biāo)記;清除則遍歷整個(gè)內(nèi)存區(qū)域,,找出其中需要進(jìn)行回收的區(qū)域;而壓縮則把存活對(duì)象的內(nèi)存移動(dòng)到整個(gè)內(nèi)存區(qū)域的一端,,使得另一端是一塊連續(xù)的空閑區(qū)域,方便進(jìn)行內(nèi)存分配和復(fù)制,。

JDK 5中提供了4種不同的垃圾回收機(jī)制,。最常用的是串行回收方式,,即使用單個(gè)CPU回收年輕和年老世代的內(nèi)存,。在回收的過(guò)程中,應(yīng)用程序被暫時(shí)中止,?;厥辗绞绞褂玫氖巧厦嫣岬降淖罨镜姆执厥?。串行回收方式適合于一般的單CPU桌面平臺(tái),。如果是多CPU的平臺(tái),則適合的是并行回收方式,。這種方式在對(duì)年輕世代進(jìn)行回收的時(shí)候,,會(huì)使用多個(gè)CPU來(lái)并行處理,,可以提升回收的性能,。并發(fā)標(biāo)記-清除回收方式適合于對(duì)應(yīng)用的響應(yīng)時(shí)間要求比較 高的情況,,即需要減少垃圾回收所帶來(lái)的應(yīng)用暫時(shí)中止的時(shí)間,。這種做法的優(yōu)點(diǎn)在于可以在應(yīng)用運(yùn)行的同時(shí)標(biāo)記存活對(duì)象與回收垃圾,而只需要暫時(shí)中止應(yīng)用比較短的時(shí)間,。

通過(guò)JDK中提供的JConsole可以很容易的查看當(dāng)前應(yīng)用的內(nèi)存使用情況,。在JVM啟動(dòng)的時(shí)候添加參數(shù) -verbose:gc 可以查看垃圾回收器的運(yùn)行結(jié)果,。

Java引用類型

如果一個(gè)內(nèi)存中的對(duì)象沒(méi)有任何引用的話,就說(shuō)明這個(gè)對(duì)象已經(jīng)不再被使用了,,從而可以成為被垃圾回收的候選,。不過(guò)由于垃圾回收器的運(yùn)行時(shí)間不確定,可被垃圾回收的對(duì)象的實(shí)際被回收時(shí)間是不確定的,。對(duì)于一個(gè)對(duì)象來(lái)說(shuō),只要有引用的存在,,它就會(huì)一直存在于內(nèi)存中,。如果這樣的對(duì)象越來(lái)越多,,超出了JVM中的內(nèi)存總數(shù),JVM就會(huì)拋出OutOfMemory錯(cuò)誤,。雖然垃圾回收的具體運(yùn)行是由JVM來(lái)控制的,但是開(kāi)發(fā)人員仍然可以在一定程度上與垃圾回收器進(jìn)行交互,,其目的在于更好的幫助垃圾回收器管理好應(yīng)用的內(nèi)存,。這種交互方式就是使用JDK 1.2引入的java.lang.ref包。

強(qiáng)引用

在一般的Java程序中,,見(jiàn)到最多的就是強(qiáng)引用(strong reference)。如Date date = new Date(),,date就是一個(gè)對(duì)象的強(qiáng)引用,。對(duì)象的強(qiáng)引用可以在程序中到處傳遞,。很多情況下,,會(huì)同時(shí)有多個(gè)引用指向同一個(gè)對(duì)象,。強(qiáng)引用的存在限制了對(duì)象在內(nèi)存中的存活時(shí)間,。假如對(duì)象A中包含了一個(gè)對(duì)象B的強(qiáng)引用,那么一般情況下,,對(duì)象B的存活時(shí)間就不會(huì)短于對(duì)象A,。如果對(duì)象A沒(méi)有顯式的把對(duì)象B的引用設(shè)為null的話,就只有當(dāng)對(duì)象A被垃圾回收之后,,對(duì)象B才不再有引用指向它,,才可能獲得被垃圾回收的機(jī)會(huì),。

除了強(qiáng)引用之外,,java.lang.ref包中提供了對(duì)一個(gè)對(duì)象的不同的引用方式,。JVM的垃圾回收器對(duì)于不同類型的引用有不同的處理方式。

軟引用

軟引用(soft reference)在強(qiáng)度上弱于強(qiáng)引用,,通過(guò)類SoftReference來(lái)表示,。它的作用是告訴垃圾回收器,,程序中的哪些對(duì)象是不那么重要,,當(dāng)內(nèi)存不足的時(shí)候是可以被暫時(shí)回收的,。當(dāng)JVM中的內(nèi)存不足的時(shí)候,垃圾回收器會(huì)釋放那些只被軟引用所指向的對(duì)象,。如果全部釋放完這些對(duì)象之后,,內(nèi)存還不足,才會(huì)拋出OutOfMemory錯(cuò)誤,。軟引用非常適合于創(chuàng)建緩存,。當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候,,緩存中的內(nèi)容是可以被釋放的。比如考慮一個(gè)圖像編輯器的程序,。該程序會(huì)把圖像文件的全部?jī)?nèi)容都讀取到內(nèi)存中,,以方便進(jìn)行處理。而用戶也可以同時(shí)打開(kāi)多個(gè)文件,。當(dāng)同時(shí)打開(kāi)的文件過(guò)多的時(shí)候,,就可能造成內(nèi)存不足。如果使用軟引用來(lái)指向圖像文件內(nèi)容的話,,垃圾回收器就可以在必要的時(shí)候回收掉這些內(nèi)存,。

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;
    }
}

在運(yùn)行上面程序的時(shí)候,可以使用 -Xmx 參數(shù)來(lái)限制JVM可用的內(nèi)存,。由于軟引用所指向的對(duì)象可能被回收掉,,在通過(guò)get方法來(lái)獲取軟引用所實(shí)際指向的對(duì)象的時(shí)候,總是要檢查該對(duì)象是否還存活,。

弱引用

弱引用(weak reference)在強(qiáng)度上弱于軟引用,,通過(guò)類WeakReference來(lái)表示。它的作用是引用一個(gè)對(duì)象,,但是并不阻止該對(duì)象被回收,。如果使用一個(gè)強(qiáng)引用的話,只要該引用存在,,那么被引用的對(duì)象是不能被回收的,。弱引用則沒(méi)有這個(gè)問(wèn)題。在垃圾回收器運(yùn)行的時(shí)候,如果一個(gè)對(duì)象的所有引用都是弱引用的話,,該對(duì)象會(huì)被回收,。弱引用的作用在于解決強(qiáng)引用所帶來(lái)的對(duì)象之間在存活時(shí)間上的耦合關(guān)系。弱引用最常見(jiàn)的用處是在集合類中,,尤其在哈希表中,。哈希表的接口允許使用任何Java對(duì)象作為鍵來(lái)使用。當(dāng)一個(gè)鍵值對(duì)被放入到哈希表中之后,,哈希表對(duì)象本身就有了對(duì)這些鍵和值對(duì)象的引用,。如果這種引用是強(qiáng)引用的話,那么只要哈希表對(duì)象本身還存活,,其中所包含的鍵和值對(duì)象是不會(huì)被回收的,。如果某個(gè)存活時(shí)間很長(zhǎng)的哈希表中包含的鍵值對(duì)很多,最終就有可能消耗掉JVM中全部的內(nèi)存,。

對(duì)于這種情況的解決辦法就是使用弱引用來(lái)引用這些對(duì)象,,這樣哈希表中的鍵和值對(duì)象都能被垃圾回收。Java中提供了WeakHashMap來(lái)滿足這一常見(jiàn)需求,。

幽靈引用

在介紹幽靈引用之前,,要先介紹Java提供的對(duì)象終止化機(jī)制(finalization)。在Object類里面有個(gè)finalize方法,,其設(shè)計(jì)的初衷是在一個(gè)對(duì)象被真正回收之前,,可以用來(lái)執(zhí)行一些清理的工作。因?yàn)镴ava并沒(méi)有提供類似C++的析構(gòu)函數(shù)一樣的機(jī)制,,就通過(guò) finalize方法來(lái)實(shí)現(xiàn),。但是問(wèn)題在于垃圾回收器的運(yùn)行時(shí)間是不固定的,所以這些清理工作的實(shí)際運(yùn)行時(shí)間也是不能預(yù)知的,。幽靈引用(phantom reference)可以解決這個(gè)問(wèn)題,。在創(chuàng)建幽靈引用PhantomReference的時(shí)候必須要指定一個(gè)引用隊(duì)列。當(dāng)一個(gè)對(duì)象的finalize方法已經(jīng)被調(diào)用了之后,,這個(gè)對(duì)象的幽靈引用會(huì)被加入到隊(duì)列中,。通過(guò)檢查該隊(duì)列里面的內(nèi)容就知道一個(gè)對(duì)象是不是已經(jīng)準(zhǔn)備要被回收了。

幽靈引用及其隊(duì)列的使用情況并不多見(jiàn),,主要用來(lái)實(shí)現(xiàn)比較精細(xì)的內(nèi)存使用控制,,這對(duì)于移動(dòng)設(shè)備來(lái)說(shuō)是很有意義的。程序可以在確定一個(gè)對(duì)象要被回收之后,,再申請(qǐng)內(nèi)存創(chuàng)建新的對(duì)象,。通過(guò)這種方式可以使得程序所消耗的內(nèi)存維持在一個(gè)相對(duì)較低的數(shù)量。比如下面的代碼給出了一個(gè)緩沖區(qū)的實(shí)現(xiàn)示例,。

public class PhantomBuffer {
  private byte[] data = new byte[0];
    private ReferenceQueue<byte[]> queue = new ReferenceQueue<byte[]>();
    private PhantomReference<byte[]> ref = new PhantomReference<byte[]>(data, queue);
    public byte[] get(int size) {
        if (size <= 0) {
            throw new IllegalArgumentException("Wrong buffer size");
        }
        if (data.length < size) {
            data = null;
            System.gc(); //強(qiáng)制運(yùn)行垃圾回收器
             try {
                queue.remove(); //該方法會(huì)阻塞直到隊(duì)列非空
                ref.clear(); //幽靈引用不會(huì)自動(dòng)清空,,要手動(dòng)運(yùn)行
                ref = null;
                data = new byte[size];
                ref = new PhantomReference<byte[]>(data, queue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
       }
       return data;
    }
}

在上面的代碼中,每次申請(qǐng)新的緩沖區(qū)的時(shí)候,,都首先確保之前的緩沖區(qū)的字節(jié)數(shù)組已經(jīng)被成功回收,。引用隊(duì)列的remove方法會(huì)阻塞直到新的幽靈引用被加入到隊(duì)列中。不過(guò)需要注意的是,,這種做法會(huì)導(dǎo)致垃圾回收器被運(yùn)行的次數(shù)過(guò)多,,可能會(huì)造成程序的吞吐量過(guò)低。

引用隊(duì)列

在有些情況下,,程序會(huì)需要在一個(gè)對(duì)象的可達(dá)到性發(fā)生變化的時(shí)候得到通知,。比如某個(gè)對(duì)象的強(qiáng)引用都已經(jīng)不存在了,只剩下軟引用或是弱引用,。但是還需要對(duì)引用本身做一些的處理,。典型的情景是在哈希表中。引用對(duì)象是作為WeakHashMap中的鍵對(duì)象的,,當(dāng)其引用的實(shí)際對(duì)象被垃圾回收之后,,就需要把該鍵值對(duì)從哈希表中刪除。有了引用隊(duì)列(ReferenceQueue),,就可以方便的獲取到這些弱引用對(duì)象,,將它們從表中刪除。在軟引用和弱引用對(duì)象被添加到隊(duì)列之前,,其對(duì)實(shí)際對(duì)象的引用會(huì)被自動(dòng)清空,。通過(guò)引用隊(duì)列的poll/remove方法就可以分別以非阻塞和阻塞的方式獲取隊(duì)列中的引用對(duì)象。

參考資料

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多