垃圾收集器與Java編程
發(fā)表時(shí)間:2024-01-17 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]垃圾收集器(Garbage Collector,GC)對(duì)Java程序員來(lái)說(shuō),基本上是透明的,但是一個(gè)優(yōu)秀的Java程序員必須了解GC的工作原理、如何優(yōu)化GC的性能、如何與GC進(jìn)行有限的交互,因?yàn)橛幸恍⿷?yīng)用程序?qū)π阅芤筝^高,例如嵌入式系統(tǒng)、實(shí)時(shí)系統(tǒng)等,只有全面提升內(nèi)存的管理效率 ,才能提高整個(gè)應(yīng)用...
垃圾收集器(Garbage Collector,GC)對(duì)Java程序員來(lái)說(shuō),基本上是透明的,但是一個(gè)優(yōu)秀的Java程序員必須了解GC的工作原理、如何優(yōu)化GC的性能、如何與GC進(jìn)行有限的交互,因?yàn)橛幸恍⿷?yīng)用程序?qū)π阅芤筝^高,例如嵌入式系統(tǒng)、實(shí)時(shí)系統(tǒng)等,只有全面提升內(nèi)存的管理效率 ,才能提高整個(gè)應(yīng)用程序的性能。本篇文章首先簡(jiǎn)單介紹GC的工作原理之后,然后再對(duì)GC的幾個(gè)關(guān)鍵問(wèn)題進(jìn)行深入探討,最后提出一些Java程序設(shè)計(jì)建議,從GC角度提高Java程序的性能。
GC的基本原理 Java的內(nèi)存管理實(shí)際上就是對(duì)象的管理,其中包括對(duì)象的分配和釋放。
對(duì)于程序員來(lái)說(shuō),分配對(duì)象使用new關(guān)鍵字;釋放對(duì)象時(shí),只要將對(duì)象所有引用賦值為null,讓程序不能夠再訪問(wèn)到這個(gè)對(duì)象,我們稱該對(duì)象為"不可達(dá)的"。GC將負(fù)責(zé)回收所有"不可達(dá)"對(duì)象的內(nèi)存空間。
對(duì)于GC來(lái)說(shuō),當(dāng)程序員創(chuàng)建對(duì)象時(shí),GC就開始監(jiān)控這個(gè)對(duì)象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對(duì)象(詳見 參考資料1 )。通過(guò)這種方式確定哪些對(duì)象是"可達(dá)的",哪些對(duì)象是"不可達(dá)的"。當(dāng)GC確定一些對(duì)象為"不可達(dá)"時(shí),GC就有責(zé)任回收這些內(nèi)存空間。但是,為了保證GC能夠在不同平臺(tái)實(shí)現(xiàn)的問(wèn)題,Java規(guī)范對(duì)GC的很多行為都沒(méi)有進(jìn)行嚴(yán)格的規(guī)定。例如,對(duì)于采用什么類型的回收算法、什么時(shí)候進(jìn)行回收等重要問(wèn)題都沒(méi)有明確的規(guī)定。因此,不同的JVM的實(shí)現(xiàn)者往往有不同的實(shí)現(xiàn)算法。這也給Java程序員的開發(fā)帶來(lái)行多不確定性。本文研究了幾個(gè)與GC工作相關(guān)的問(wèn)題,努力減少這種不確定性給Java程序帶來(lái)的負(fù)面影響。
增量式GC( Incremental GC ) GC在JVM中通常是由一個(gè)或一組進(jìn)程來(lái)實(shí)現(xiàn)的,它本身也和用戶程序一樣占用heap空間,運(yùn)行時(shí)也占用CPU。當(dāng)GC進(jìn)程運(yùn)行時(shí),應(yīng)用程序停止運(yùn)行。因此,當(dāng)GC運(yùn)行時(shí)間較長(zhǎng)時(shí),用戶能夠感到Java程序的停頓,另外一方面,如果GC運(yùn)行時(shí)間太短,則可能對(duì)象回收率太低,這意味著還有很多應(yīng)該回收的對(duì)象沒(méi)有被回收,仍然占用大量?jī)?nèi)存。因此,在設(shè)計(jì)GC的時(shí)候,就必須在停頓時(shí)間和回收率之間進(jìn)行權(quán)衡。一個(gè)好的GC實(shí)現(xiàn)允許用戶定義自己所需要的設(shè)置,例如有些內(nèi)存有限有設(shè)備,對(duì)內(nèi)存的使用量非常敏感,希望GC能夠準(zhǔn)確的回收內(nèi)存,它并不在意程序速度的放慢。另外一些實(shí)時(shí)網(wǎng)絡(luò)游戲,就不能夠允許程序有長(zhǎng)時(shí)間的中斷。增量式GC就是通過(guò)一定的回收算法,把一個(gè)長(zhǎng)時(shí)間的中斷,劃分為很多個(gè)小的中斷,通過(guò)這種方式減少GC對(duì)用戶程序的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程序的最長(zhǎng)停頓時(shí)間。
Sun JDK提供的HotSpot JVM就能支持增量式GC。HotSpot JVM缺省GC方式為不使用增量GC,為了啟動(dòng)增量GC,我們必須在運(yùn)行Java程序時(shí)增加-Xincgc的參數(shù)。HotSpot JVM增量式GC的實(shí)現(xiàn)是采用Train GC算法。它的基本想法就是,將堆中的所有對(duì)象按照創(chuàng)建和使用情況進(jìn)行分組(分層),將使用頻繁高和具有相關(guān)性的對(duì)象放在一隊(duì)中,隨著程序的運(yùn)行,不斷對(duì)組進(jìn)行調(diào)整。當(dāng)GC運(yùn)行時(shí),它總是先回收最老的(最近很少訪問(wèn)的)的對(duì)象,如果整組都為可回收對(duì)象,GC將整組回收。這樣,每次GC運(yùn)行只回收一定比例的不可達(dá)對(duì)象,保證程序的順暢運(yùn)行。
詳解finalize函數(shù) finalize是位于Object類的一個(gè)方法,該方法的訪問(wèn)修飾符為protected,由于所有類為Object的子類,因此用戶類很容易訪問(wèn)到這個(gè)方法。由于,finalize函數(shù)沒(méi)有自動(dòng)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,我們必須手動(dòng)的實(shí)現(xiàn),因此finalize函數(shù)的最后一個(gè)語(yǔ)句通常是super.finalize()。通過(guò)這種方式,我們可以實(shí)現(xiàn)從下到上實(shí)現(xiàn)finalize的調(diào)用,即先釋放自己的資源,然后再釋放父類的資源。
根據(jù)Java語(yǔ)言規(guī)范,JVM保證調(diào)用finalize函數(shù)之前,這個(gè)對(duì)象是不可達(dá)的,但是JVM不保證這個(gè)函數(shù)一定會(huì)被調(diào)用。另外,規(guī)范還保證finalize函數(shù)最多運(yùn)行一次。
很多Java初學(xué)者會(huì)認(rèn)為這個(gè)方法類似與C++中的析構(gòu)函數(shù),將很多對(duì)象、資源的釋放都放在這一函數(shù)里面。其實(shí),這不是一種很好的方式。原因有三,其一,GC為了能夠支持finalize函數(shù),要對(duì)覆蓋這個(gè)函數(shù)的對(duì)象作很多附加的工作。其二,在finalize運(yùn)行完成之后,該對(duì)象可能變成可達(dá)的,GC還要再檢查一次該對(duì)象是否是可達(dá)的。因此,使用finalize會(huì)降低GC的運(yùn)行性能。其三,由于GC調(diào)用finalize的時(shí)間是不確定的,因此通過(guò)這種方式釋放資源也是不確定的。
通常,finalize用于一些不容易控制、并且非常重要資源的釋放,例如一些I/O的操作,數(shù)據(jù)的連接。這些資源的釋放對(duì)整個(gè)應(yīng)用程序是非常關(guān)鍵的。在這種情況下,程序員應(yīng)該以通過(guò)程序本身管理(包括釋放)這些資源為主,以finalize函數(shù)釋放資源方式為輔,形成一種雙保險(xiǎn)的管理機(jī)制,而不應(yīng)該僅僅依靠finalize來(lái)釋放資源。