Effective Java 2.0_中文版_Item 7

文章作者:Tyan
博客:noahsnail.com | CSDN | 簡(jiǎn)書(shū)

Item 7: 避免使用finalizers(終結(jié)方法霍殴,Java模擬C++的析構(gòu)函數(shù))

終結(jié)方法通常是不可預(yù)測(cè)的普办,經(jīng)常是危險(xiǎn)的座硕,一般來(lái)說(shuō)是沒(méi)必要的。使用它們會(huì)引起不穩(wěn)定的行為拆檬,性能變低栓始,可移植性問(wèn)題等。終結(jié)方法有一些有效的使用硼身,這個(gè)在本條目的后面會(huì)講到硅急,但根據(jù)經(jīng)驗(yàn),你應(yīng)該避免使用終結(jié)方法佳遂。

C++程序員被警告說(shuō)不要去想像Java中模擬C++析構(gòu)函數(shù)那樣的終結(jié)方法营袜。在C++中,析構(gòu)函數(shù)是一種正吵笞铮回收對(duì)象資源的方式荚板,是構(gòu)造函數(shù)的必要對(duì)應(yīng)。在Java中吩屹,當(dāng)對(duì)象不可訪問(wèn)時(shí)啸驯,垃圾回收器會(huì)回收對(duì)象的相關(guān)資源,不需要程序員進(jìn)行專門的工作祟峦。C++析構(gòu)函數(shù)也用來(lái)回收其它的非內(nèi)存資源。在Java中徙鱼,try-finally塊用來(lái)完成這樣的功能宅楞。

終結(jié)方法的一個(gè)缺點(diǎn)是不能保證它們及時(shí)的執(zhí)行[JLS,12.6]袱吆。從對(duì)象變得不可訪問(wèn)開(kāi)始到它的終結(jié)方法被執(zhí)行結(jié)束厌衙,這中間的時(shí)間可以任意長(zhǎng)。這意味著你不應(yīng)該在終結(jié)方法中做任何時(shí)間為關(guān)鍵的事情绞绒。例如婶希,依賴終結(jié)方法來(lái)關(guān)閉文件是一個(gè)嚴(yán)重的錯(cuò)誤,因?yàn)殚_(kāi)放的文件描述符是一種有限的資源蓬衡。如果許多文件都是打開(kāi)狀態(tài)喻杈,由于JVM執(zhí)行終結(jié)方法時(shí)是遲緩的,因此程序可能失敗狰晚,因?yàn)樗荒茉俅蜷_(kāi)文件筒饰。

盡快執(zhí)行終結(jié)方法是垃圾回收算法的主要功能,在不同的JVM實(shí)現(xiàn)中變化很大壁晒。依賴終結(jié)方法執(zhí)行及時(shí)性的程序同樣變化很大瓷们。一個(gè)程序在測(cè)試它的JVM上運(yùn)行非常完美,但在你最重要客戶支持的JVM上它卻糟糕地運(yùn)行失敗了秒咐,這是完全有可能的谬晕。

遲緩終結(jié)不僅僅是一個(gè)理論問(wèn)題。在很少的情況下携取,為一個(gè)類提供終結(jié)方法可能會(huì)隨意地延遲它實(shí)例的回收攒钳。有個(gè)同事調(diào)試一個(gè)長(zhǎng)期運(yùn)行的GUI應(yīng)用,程序莫名其妙的死掉了歹茶,拋出了OutOfMemoryError錯(cuò)誤夕玩。分析表明在程序死亡時(shí)你弦,應(yīng)用中的終結(jié)方法隊(duì)列中有成千上萬(wàn)的圖形對(duì)象在等待被終結(jié)并回收。遺憾的是燎孟,終結(jié)方法線程的運(yùn)行優(yōu)先級(jí)要低于另一個(gè)應(yīng)用線程禽作,因此在另一個(gè)應(yīng)用線程中的對(duì)象變得可以被終結(jié)時(shí),它們不能被終結(jié)揩页。語(yǔ)言規(guī)范不能保證哪一個(gè)線程來(lái)執(zhí)行終結(jié)方法旷偿,因此沒(méi)有輕便的方式來(lái)阻止這種問(wèn)題的發(fā)生戏自,除非避免使用終結(jié)方法烁落。

不僅語(yǔ)言規(guī)范不能保證終結(jié)方法及時(shí)的執(zhí)行;而且也不能保證終結(jié)方法得到執(zhí)行剧董。這完全有可能兔仰,甚至有可能一個(gè)程序終止時(shí)茫负,一些不能訪問(wèn)的對(duì)象的終結(jié)方法都沒(méi)有執(zhí)行。結(jié)論就是:你從不該依賴終結(jié)方法來(lái)更新重要的持續(xù)狀態(tài)乎赴。例如忍法,依賴一個(gè)終結(jié)方法來(lái)釋放一個(gè)共享資源,例如數(shù)據(jù)庫(kù)榕吼,的持續(xù)鎖饿序,很容易引起整個(gè)分布式系統(tǒng)當(dāng)?shù)簟?/p>

不要被System.gcSystem.runFinalization方法誘惑。它們可能會(huì)增加終結(jié)方法得到執(zhí)行的幾率羹蚣,但它們不能保證它原探。能保證終結(jié)方法執(zhí)行的唯一方法是System.runFinalizersOnExit以及它臭名昭著的孿生兄弟Runtime.runFinalizersOnExit。這些方法都有致命的缺陷并且已經(jīng)被廢棄了[ThreadStop]顽素。

以防你還不相信終結(jié)方法應(yīng)該被避免咽弦,這兒有另一個(gè)情況值得思考:如果在終結(jié)方法執(zhí)行期間拋出了一個(gè)無(wú)法捕獲的異常,這個(gè)異常被忽略了戈抄,對(duì)象的終結(jié)方法終止了[JLS离唬,12.6]。不能捕獲的異郴耄可能會(huì)使對(duì)象處于崩潰狀態(tài)输莺。如果另一個(gè)線程試圖使用這樣一個(gè)崩潰的對(duì)象,任何不確定性的行為都有可能發(fā)送裸诽。通常嫂用,一個(gè)未被捕獲的異常會(huì)終止線程并打印棧軌跡,但如果它發(fā)生在一個(gè)終結(jié)方法中丈冬,將不會(huì)打印出警告嘱函。

哦,還有一件事:使用終結(jié)方法會(huì)有嚴(yán)重的性能問(wèn)題埂蕊。在我的機(jī)器上往弓,創(chuàng)建并銷毀一個(gè)簡(jiǎn)單對(duì)象大約是5.6納秒疏唾。添加一個(gè)終結(jié)方法會(huì)將這個(gè)時(shí)間增加到2400納秒。換句話說(shuō)函似,創(chuàng)建一個(gè)對(duì)象并用終結(jié)方法銷毀對(duì)象比正常情況下大約慢了430倍槐脏。

因此當(dāng)一個(gè)類的對(duì)象封裝的資源需要結(jié)束時(shí),你應(yīng)該用什么來(lái)代替一個(gè)類的終結(jié)方法撇寞?例如文件或線程顿天?提供一個(gè)顯式的結(jié)束方法,當(dāng)類的實(shí)例不再需要時(shí)蔑担,要求類的客戶端在每個(gè)實(shí)例上都調(diào)用這個(gè)方法牌废。一個(gè)值得提及的細(xì)節(jié)是,實(shí)例必須跟蹤它是否已經(jīng)被終結(jié):顯式的終結(jié)方法必須記錄在一個(gè)私有字段中啤握,這個(gè)字段表明對(duì)象不再有效鸟缕,如果其它方法再對(duì)象終結(jié)后調(diào)用對(duì)象,其它方法必須檢查這個(gè)字段并拋出IllegalStateException排抬。

顯式結(jié)束方法的典型例子是InputStream叁扫,OutputStreamjava.sql.Connection的關(guān)閉方法。另一個(gè)例子是java.util.Timercancel方法畜埋,它會(huì)進(jìn)行必要的狀態(tài)檢查并一起線程相關(guān)的Timer實(shí)例平穩(wěn)的結(jié)束它自己。java.awt的例子包括Graphics.disposeWindow.dispose畴蒲。這些方法經(jīng)常被忽視悠鞍,可以預(yù)料會(huì)引起可怕的性能后果。一個(gè)相關(guān)的方法是Image.flush模燥,它會(huì)釋放所有Image實(shí)例相關(guān)的資源咖祭,但會(huì)將實(shí)例保持在仍可用的狀態(tài),如果必要的時(shí)候重新分配資源蔫骂。

顯式結(jié)束方法通過(guò)與try-finally結(jié)構(gòu)結(jié)合來(lái)確保終結(jié)么翰。在finally語(yǔ)句塊的內(nèi)部調(diào)用顯式的結(jié)束方法來(lái)確保它得到執(zhí)行,即使對(duì)象使用時(shí)拋出了一個(gè)異常:

// try-finally block guarantees execution of termination method
   Foo foo = new Foo(...);
   try {
       // Do what must be done with foo
       ...
   } finally {
       foo.terminate();  // Explicit termination method
   }

那終結(jié)方法有什么好處呢辽旋?有兩種可能的合法應(yīng)用浩嫌。一個(gè)是作為『安全網(wǎng)』,以防對(duì)象擁有者忘記調(diào)用它的顯式結(jié)束方法补胚。但這不能保證終結(jié)方法得到及時(shí)的調(diào)用码耐,當(dāng)客戶端調(diào)用顯式結(jié)束方法失敗時(shí),在那種情況下(希望很少)溶其,后面釋放資源總比不釋放資源要好骚腥。但終結(jié)方法如果發(fā)現(xiàn)資源仍沒(méi)有被釋放,它應(yīng)該輸出一個(gè)警告瓶逃,因?yàn)檫@意味著客戶端代碼存在一個(gè)BUG束铭,它應(yīng)該被修正廓块。如果你正在考慮寫這樣一個(gè)安全網(wǎng)終結(jié)方法,要仔細(xì)思考這種額外的保護(hù)是否值得額外的代價(jià)契沫。

作為顯式結(jié)束方法模式引用的四個(gè)例子(FileInputStream带猴,FileOutputStreamTimerConnection)都有終結(jié)方法作為安全網(wǎng)以防它們的結(jié)束方法沒(méi)有被調(diào)用埠褪。遺憾的是這些終結(jié)方法不輸出警告浓利。這種警告通常在API發(fā)布后不能進(jìn)行添加,因?yàn)樗鼤?huì)損壞現(xiàn)有的客戶端钞速。

終結(jié)方法的第二個(gè)合法使用是關(guān)于對(duì)象的本地對(duì)等體贷掖。本地對(duì)等體是一個(gè)本地對(duì)象,普通對(duì)象通過(guò)本地方法委托給本地對(duì)象渴语。由于本地對(duì)等體不是一個(gè)正常的對(duì)象苹威,當(dāng)它的Java對(duì)等體回收時(shí),垃圾回收器不知道并且不能回收它驾凶。假設(shè)本地對(duì)等體不擁有重要的資源牙甫,終結(jié)方法是執(zhí)行這個(gè)任務(wù)的合適工具。如果本地對(duì)等體擁有必須及時(shí)終止的資源调违,這個(gè)類應(yīng)該有一個(gè)顯式的結(jié)束方法窟哺,如上所述。結(jié)束方法應(yīng)該用來(lái)釋放重要資源技肩。結(jié)束方法可以是一個(gè)本地方法或它可以調(diào)用一個(gè)本地方法且轨。

很重要的一點(diǎn)就是要注意『終結(jié)方法鏈』是不能自動(dòng)執(zhí)行的。如果一個(gè)類(不是Object)有一個(gè)終結(jié)方法虚婿,一個(gè)子類覆寫了它旋奢,子類終結(jié)方法必須手動(dòng)調(diào)用父類終結(jié)方法。你應(yīng)該try塊內(nèi)終止這個(gè)子類并在對(duì)應(yīng)的finally塊調(diào)用父類終結(jié)方法然痊。這保證了父類終結(jié)方法得到了執(zhí)行至朗,即使子類終結(jié)方法拋出異常,反之亦然剧浸。下面是它的一個(gè)例子锹引、注意這個(gè)例子使用了Override注解(@Override),在release 1.5版本中添加∷粝悖現(xiàn)在你可以忽略Override注解粤蝎,或看Item 36弄明白它是什么意思:


// Manual finalizer chaining
@Override 
protected void finalize() throws Throwable {
    try {
        ... // Finalize subclass state
    } finally {
        super.finalize();
    } 
}
// Finalizer Guardian idiom
public class Foo {
    // Sole purpose of this object is to finalize outer Foo object
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
            ... // Finalize outer Foo object
        }
    };
    ...  // Remainder omitted
}

注意公有類Foo沒(méi)有終結(jié)方法(除非它從Object繼承一個(gè)無(wú)關(guān)緊要的),因此子類的終結(jié)方法是否調(diào)用super.finalize是不重要的袋马。每一個(gè)含有終結(jié)方法的非終結(jié)公有類都應(yīng)該考慮這個(gè)技術(shù)初澎。

總結(jié):不要使用終結(jié)方法,除非是用作安全網(wǎng)或用來(lái)終止一個(gè)非重要的本地資源。在那些你使用終結(jié)方法的稀少實(shí)例中碑宴,記住調(diào)用super.finalize软啼。如果你使用終結(jié)方法作為安全網(wǎng),記住在終結(jié)方法中輸出非法用法延柠。最后祸挪,如果你需要將終結(jié)方法關(guān)聯(lián)到一個(gè)公有的,非終結(jié)類贞间,考慮使用終結(jié)方法守護(hù)者贿条,即使子類終結(jié)方法調(diào)用super.finalize失敗,也會(huì)進(jìn)行終結(jié)增热。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末整以,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子峻仇,更是在濱河造成了極大的恐慌公黑,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摄咆,死亡現(xiàn)場(chǎng)離奇詭異凡蚜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)吭从,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門朝蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人涩金,你說(shuō)我怎么就攤上這事芹务。” “怎么了鸭廷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熔吗。 經(jīng)常有香客問(wèn)我辆床,道長(zhǎng),這世上最難降的妖魔是什么桅狠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任讼载,我火速辦了婚禮,結(jié)果婚禮上中跌,老公的妹妹穿的比我還像新娘咨堤。我一直安慰自己,他們只是感情好漩符,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布一喘。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凸克。 梳的紋絲不亂的頭發(fā)上议蟆,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音萎战,去河邊找鬼咐容。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚂维,可吹牛的內(nèi)容都是我干的戳粒。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼虫啥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔚约!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起孝鹊,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炊琉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后又活,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體苔咪,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年柳骄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了团赏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耐薯,死狀恐怖舔清,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情曲初,我是刑警寧澤体谒,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站臼婆,受9級(jí)特大地震影響抒痒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颁褂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一故响、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颁独,春花似錦彩届、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春坯墨,著一層夾襖步出監(jiān)牢的瞬間寂汇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工捣染, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骄瓣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓耍攘,卻偏偏與公主長(zhǎng)得像榕栏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蕾各,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 文章作者:Tyan博客:noahsnail.com | CSDN | 簡(jiǎn)書(shū) Item 7: Avoid f...
    SnailTyan閱讀 487評(píng)論 4 0
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法扒磁,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法式曲,繼承相關(guān)的語(yǔ)法妨托,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,664評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在吝羞,面了一些公司兰伤,掛了不少,但最終還是拿到小米钧排、百度敦腔、阿里、京東恨溜、新浪符衔、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,278評(píng)論 11 349
  • Day 25--- 行書(shū)營(yíng)《持續(xù)的幸冈阍》 20170302 魯迅先生說(shuō)過(guò):時(shí)間判族,每天得到的都是二十四小時(shí),可是一天...
    橙長(zhǎng)之路閱讀 384評(píng)論 1 1
  • 1.實(shí)現(xiàn)一個(gè)瀑布流布局效果 JQ 瀑布流-1 效果 2.實(shí)現(xiàn)木桶布局效果 JQ 木桶布局 效果 3.實(shí)現(xiàn)一個(gè)新聞瀑...
    饑人谷_米彌輪閱讀 361評(píng)論 0 1