<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    迷途書童

    敏感、勤學(xué)、多思
    隨筆 - 77, 文章 - 4, 評(píng)論 - 86, 引用 - 0
    數(shù)據(jù)加載中……

    對(duì)象引用是怎樣嚴(yán)重影響垃圾收集器的

      如果您認(rèn)為 Java 游戲開發(fā)人員是 Java 編程世界的一級(jí)方程式賽車手,那么您就會(huì)明白為什么他們會(huì)如此地重視程序的性能。 游戲開發(fā)人員幾乎每天都要面對(duì)的性能問題,往往超過了一般程序員考慮問題的范圍。哪里可以找到這些特殊的開發(fā)人員呢?Java 游戲社區(qū)就是一個(gè)好去處(參見 參考資料)。 雖然在這個(gè)站點(diǎn)可能沒有很多關(guān)于服務(wù)器端的應(yīng)用,但是我們依然可以從中受益,看看這些“惜比特如金”的游戲開發(fā)人員每天所面對(duì)的,我們往往能從中得到寶貴的經(jīng)驗(yàn)。讓我們開始游戲吧!

      對(duì)象泄漏
      游戲程序員跟其他程序員一樣――他們也需要理解 Java 運(yùn)行時(shí)環(huán)境的一些微妙之處,比如垃圾收集。垃圾收集可能是使您感到難于理解的較難的概念之一, 因?yàn)樗⒉荒芸偸呛翢o(wú)遺漏地解決 Java 運(yùn)行時(shí)環(huán)境中堆管理的問題。似乎有很多類似這樣的討論,它的開頭或結(jié)尾寫著:“我的問題是關(guān)于垃圾收集”。

      假如您正面遭遇內(nèi)存耗盡(out-of-memory)的錯(cuò)誤。于是您使用檢測(cè)工具想要找到問題所在,但這是徒勞的。您很容易想到另外一個(gè)比較可信的原因:這是 Java 虛擬機(jī)堆管理的問題,而不會(huì)認(rèn)為這是您自己的程序的緣故。但是,正如 Java 游戲社區(qū)的資深專家不止一次地解釋的,Java 虛擬機(jī)并不存在任何被證實(shí)的對(duì)象泄漏問題。實(shí)踐證明,垃圾收集器一般能夠精確地判斷哪些對(duì)象可被收集,并且重新收回它們的內(nèi)存空間給 Java 虛擬機(jī)。所以,如果您遇到了內(nèi)存耗盡的錯(cuò)誤,那么這完全可能是由您的程序造成的,也就是說(shuō)您的程序中存在著“無(wú)意識(shí)的對(duì)象保留(unintentional object retention)”。

      內(nèi)存泄漏與無(wú)意識(shí)的對(duì)象保留
      內(nèi)存泄漏和無(wú)意識(shí)的對(duì)象保留的區(qū)別是什么呢?對(duì)于用 Java 語(yǔ)言編寫的程序來(lái)說(shuō),確實(shí)沒有區(qū)別。兩者都是指在您的程序中存在一些對(duì)象引用,但實(shí)際上您并不需要引用這些對(duì)象。一個(gè)典型的例子是向一個(gè)集合中加入一些對(duì)象以便以后使用它們,但是您卻忘了在使用完以后從集合中刪除這些對(duì)象。因?yàn)榧峡梢詿o(wú)限制地?cái)U(kuò)大,并且從來(lái)不會(huì)變小,所以當(dāng)您在集合中加入了太多的對(duì)象(或者是有很多的對(duì)象被集合中的元素所引用)時(shí),您就會(huì)因?yàn)槎训目臻g被填滿而導(dǎo)致內(nèi)存耗盡的錯(cuò)誤。垃圾收集器不能收集這些您認(rèn)為已經(jīng)用完的對(duì)象,因?yàn)閷?duì)于垃圾收集器來(lái)說(shuō),應(yīng)用程序仍然可以通過這個(gè)集合在任何時(shí)候訪問這些對(duì)象,所以這些對(duì)象是不可能被當(dāng)作垃圾的。

      對(duì)于沒有垃圾收集的語(yǔ)言來(lái)說(shuō),例如 C++ ,內(nèi)存泄漏和無(wú)意識(shí)的對(duì)象保留是有區(qū)別的。C++ 程序跟 Java 程序一樣,可能產(chǎn)生無(wú)意識(shí)的對(duì)象保留。但是 C++ 程序中存在真正的內(nèi)存泄漏,即應(yīng)用程序無(wú)法訪問一些對(duì)象以至于被這些對(duì)象使用的內(nèi)存無(wú)法釋放且返還給系統(tǒng)。令人欣慰的是,在 Java 程序中,這種內(nèi)存泄漏是不可能出現(xiàn)的。所以,我們更喜歡用“無(wú)意識(shí)的對(duì)象保留”來(lái)表示這個(gè)令 Java 程序員抓破頭皮的內(nèi)存問題。這樣,我們就能區(qū)別于其他使用沒有垃圾收集語(yǔ)言的程序員。

      跟蹤被保留的對(duì)象
      那么當(dāng)發(fā)現(xiàn)了無(wú)意識(shí)的對(duì)象保留該怎么辦呢?首先,需要確定哪些對(duì)象是被無(wú)意保留的,并且需要找到究竟是哪些對(duì)象在引用它們。然后必須安排好 應(yīng)該在哪里釋放它們。最容易的方法是使用能夠?qū)Χ旬a(chǎn)生快照的檢測(cè)工具來(lái)標(biāo)識(shí)這些對(duì)象,比較堆的快照中對(duì)象的數(shù)目,跟蹤這些對(duì)象,找到引用這些對(duì)象的對(duì)象,然后強(qiáng)制進(jìn)行垃圾收集。有了這樣一個(gè)檢測(cè)器,接下來(lái)的工作相對(duì)而言就比較簡(jiǎn)單了:

      等待直到系統(tǒng)達(dá)到一個(gè)穩(wěn)定的狀態(tài),這個(gè)狀態(tài)下大多數(shù)新產(chǎn)生的對(duì)象都是暫時(shí)的,符合被收集的條件;這種狀態(tài)一般在程序所有的初始化工作都完成了之后。
      強(qiáng)制進(jìn)行一次垃圾收集,并且對(duì)此時(shí)的堆做一份對(duì)象快照。
      進(jìn)行任何可以產(chǎn)生無(wú)意地保留的對(duì)象的操作。
      再?gòu)?qiáng)制進(jìn)行一次垃圾收集,然后對(duì)系統(tǒng)堆中的對(duì)象做第二次對(duì)象快照。
      比較兩次快照,看看哪些對(duì)象的被引用數(shù)量比第一次快照時(shí)增加了。因?yàn)槟诳煺罩皬?qiáng)制進(jìn)行了垃圾收集,那么剩下的對(duì)象都應(yīng)該是被應(yīng)用程序所引用的對(duì)象,并且通過比較兩次快照我們可以準(zhǔn)確地找出那些被程序保留的、新產(chǎn)生的對(duì)象。
      根據(jù)您對(duì)應(yīng)用程序本身的理解,并且根據(jù)對(duì)兩次快照的比較,判斷出哪些對(duì)象是被無(wú)意保留的。
      跟蹤這些對(duì)象的引用鏈,找出究竟是哪些對(duì)象在引用這些無(wú)意地保留的對(duì)象,直到您找到了那個(gè)根對(duì)象,它就是產(chǎn)生問題的根源。

      顯式地賦空(nulling)變量
      一談到垃圾收集這個(gè)主題,總會(huì)涉及到這樣一個(gè)吸引人的討論,即顯式地賦空變量是否有助于程序的性能。賦空變量是指簡(jiǎn)單地將 null 值顯式地賦值給這個(gè)變量,相對(duì)于讓該變量的引用失去其作用域。

    清單 1. 局部作用域

    public static String scopingExample(String string) {
    ? StringBuffer sb = new StringBuffer();
    ? sb.append("hello ").append(string);
    ? sb.append(", nice to see you!");
    ? return sb.toString();
    }

      當(dāng)該方法執(zhí)行時(shí),運(yùn)行時(shí)棧保留了一個(gè)對(duì) StringBuffer 對(duì)象的引用,這個(gè)對(duì)象是在程序的第一行產(chǎn)生的。在這個(gè)方法的整個(gè)執(zhí)行期間,棧保存的這個(gè)對(duì)象引用將會(huì)防止該對(duì)象被當(dāng)作垃圾。當(dāng)這個(gè)方法執(zhí)行完畢,變量 sb 也就失去了它的作用域,相應(yīng)地運(yùn)行時(shí)棧就會(huì)刪除對(duì)該 StringBuffer 對(duì)象的引用。于是不再有對(duì)該 StringBuffer 對(duì)象的引用,現(xiàn)在它就可以被當(dāng)作垃圾收集了。棧刪除引用的操作就等于在該方法結(jié)束時(shí)將 null 值賦給變量 sb。

      錯(cuò)誤的作用域
      既然 Java 虛擬機(jī)可以執(zhí)行等價(jià)于賦空的操作,那么顯式地賦空變量還有什么用呢?對(duì)于在正確的作用域中的變量來(lái)說(shuō),顯式地賦空變量的確沒用。但是讓我們來(lái)看看另外一個(gè)版本的 scopingExample 方法,這一次我們將把變量 sb 放在一個(gè)錯(cuò)誤的作用域中。

    清單 2. 靜態(tài)作用域

    static StringBuffer sb = new StringBuffer();
    public static String scopingExample(String string) {
    ? sb = new StringBuffer();
    ? sb.append("hello ").append(string);
    ? sb.append(", nice to see you!");
    ? return sb.toString();
    }

      現(xiàn)在 sb 是一個(gè)靜態(tài)變量,所以只要它所在的類還裝載在 Java 虛擬機(jī)中,它也將一直存在。該方法執(zhí)行一次,一個(gè)新的 StringBuffer 將被創(chuàng)建并且被 sb 變量引用。在這種情況下,sb 變量以前引用的 StringBuffer 對(duì)象將會(huì)死亡,成為垃圾收集的對(duì)象。也就是說(shuō),這個(gè)死亡的 StringBuffer 對(duì)象被程序保留的時(shí)間比它實(shí)際需要保留的時(shí)間長(zhǎng)得多――如果再也沒有對(duì)該 scopingExample 方法的調(diào)用,它將會(huì)永遠(yuǎn)保留下去。

      一個(gè)有問題的例子
      即使如此,顯式地賦空變量能夠提高性能嗎?我們會(huì)發(fā)現(xiàn)我們很難相信一個(gè)對(duì)象會(huì)或多或少對(duì)程序的性能產(chǎn)生很大影響,直到我看到了一個(gè)在 Java Games 的 Sun 工程師給出的一個(gè)例子,這個(gè)例子包含了一個(gè)不幸的大型對(duì)象。

    清單 3. 仍在靜態(tài)作用域中的對(duì)象

    private static Object bigObject;

    public static void test(int size) {
    ? long startTime = System.currentTimeMillis();
    ? long numObjects = 0;
    ? while (true) {
    ??? //bigObject = null; //explicit nulling
    ??? //SizableObject could simply be a large array, e.g. byte[]
    ??? //In the JavaGaming discussion it was a BufferedImage
    ??? bigObject = new SizableObject(size);
    ??? long endTime = System.currentTimeMillis();
    ??? ++numObjects;
    ??? // We print stats for every two seconds
    ??? if (endTime - startTime >= 2000) {
    ????? System.out.println("Objects created per 2 seconds = " + numObjects);
    ????? startTime = endTime;
    ????? numObjects = 0;
    ??? }
    ? }
    }

      這個(gè)例子有個(gè)簡(jiǎn)單的循環(huán),創(chuàng)建一個(gè)大型對(duì)象并且將它賦給同一個(gè)變量,每隔兩秒鐘報(bào)告一次所創(chuàng)建的對(duì)象個(gè)數(shù)。現(xiàn)在的 Java 虛擬機(jī)采用 generational 垃圾收集機(jī)制,新的對(duì)象創(chuàng)建之后放在一個(gè)內(nèi)存空間(取名 Eden)內(nèi),然后將那些在第一次垃圾收集以后仍然保留的對(duì)象轉(zhuǎn)移到另外一個(gè)內(nèi)存空間。在 Eden,即創(chuàng)建新對(duì)象時(shí)所在的新一代空間中,收集對(duì)象要比在“老一代”空間中快得多。但是如果 Eden 空間已經(jīng)滿了,沒有空間可供分配,那么就必須把 Eden 中的對(duì)象轉(zhuǎn)移到老一代空間中,騰出空間來(lái)給新創(chuàng)建的對(duì)象。如果沒有顯式地賦空變量,而且所創(chuàng)建的對(duì)象足夠大,那么 Eden 就會(huì)填滿,并且垃圾收集器就不能收集當(dāng)前所引用的這個(gè)大型對(duì)象。所產(chǎn)生的后果是,這個(gè)大型對(duì)象被轉(zhuǎn)移到“老一代空間”,并且要花更多的時(shí)間來(lái)收集它。

      通過顯式地賦空變量,Eden 就能在新對(duì)象創(chuàng)建之前獲得自由空間,這樣垃圾收集就會(huì)更快。實(shí)際上,在顯式賦空的情況下,該循環(huán)在兩秒鐘內(nèi)創(chuàng)建的對(duì)象個(gè)數(shù)是沒有顯式賦空時(shí)的5倍――但是僅當(dāng)您選擇創(chuàng)建的對(duì)象要足夠大而可以填滿 Eden 時(shí)才是如此, 在 Windows 環(huán)境、Java虛擬機(jī) 1.4 的默認(rèn)配置下大概需要 500KB。那就是一行賦空操作產(chǎn)生的 5 倍的性能差距。但是請(qǐng)注意這個(gè)性能差別產(chǎn)生的原因是變量的作用域不正確,這正是賦空操作發(fā)揮作用的地方,并且是因?yàn)樗鶆?chuàng)建的對(duì)象非常大。

      最佳實(shí)踐
      這是一個(gè)有趣的例子,但是值得強(qiáng)調(diào)的是,最佳實(shí)踐是正確地設(shè)置變量的作用域,而不要顯式地賦空它們。雖然顯式賦空變量一般應(yīng)該沒有影響,但總有一些反面的例子證明這樣做會(huì)對(duì)性能產(chǎn)生巨大的負(fù)面影響。例如,迭代地或者遞歸地賦空集合內(nèi)的元素使得這些集合中的對(duì)象能夠滿足垃圾收集的條件,實(shí)際上是增加了系統(tǒng)的開銷而不是幫助垃圾收集。請(qǐng)記住這是個(gè)有意弄錯(cuò)作用域的例子,其實(shí)質(zhì)是一個(gè)無(wú)意識(shí)的對(duì)象保留的例子。

    posted on 2006-04-25 09:35 迷途書童 閱讀(282) 評(píng)論(0)  編輯  收藏 所屬分類: 深入jvm


    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
    博客園   IT新聞   Chat2DB   C++博客   博問  
     
    主站蜘蛛池模板: 国产精品亚洲视频| 久久久久亚洲精品天堂| 亚洲美女高清一区二区三区 | xvideos永久免费入口| 亚洲愉拍99热成人精品热久久| 日韩免费人妻AV无码专区蜜桃| 亚洲精品无码99在线观看| 国产免费网站看v片在线| 亚洲一级免费毛片| 你懂的网址免费国产| 国产亚洲一区区二区在线| 精品国产污污免费网站aⅴ| 国产成人不卡亚洲精品91| 亚洲成A人片777777| 日韩黄色免费观看| 国产无遮挡裸体免费视频在线观看| 99热亚洲色精品国产88| 丁香五月亚洲综合深深爱| 十八禁视频在线观看免费无码无遮挡骂过| 亚洲精品福利在线观看| 免费观看国产精品| 最近2022中文字幕免费视频| 免费无遮挡无码视频在线观看| 亚洲高清视频免费| 大陆一级毛片免费视频观看i| 成全视频免费观看在线看| 亚洲欧美精品午睡沙发| 亚洲精品自产拍在线观看动漫| 国产在线a不卡免费视频| 亚洲成人免费电影| 岛国岛国免费V片在线观看| 久久亚洲中文无码咪咪爱| 久久久久亚洲AV无码网站| 最新亚洲成av人免费看| 国产精品深夜福利免费观看| a拍拍男女免费看全片| 日韩成人免费视频| 国产免费内射又粗又爽密桃视频 | 亚洲91精品麻豆国产系列在线 | 亚洲综合丁香婷婷六月香| 久久精品国产69国产精品亚洲|