[java]垃圾收集器和堆

java是一門面向?qū)ο笳Z言。在java里,除了8種基本類型外,其他的都是對(duì)象。java的對(duì)象存放在堆(heap)上。每當(dāng)你new一個(gè)對(duì)象,就會(huì)在堆上搶占一定的內(nèi)存。java虛擬機(jī)的垃圾收集器(gc)一直在后臺(tái)工作,當(dāng)它確定某些對(duì)象不再被使用時(shí),就會(huì)銷毀它,回收堆上的內(nèi)存。java垃圾收集器不是使用單純的引用計(jì)數(shù)來確定一個(gè)對(duì)象是否已不再被使用,而是使用一種相對(duì)復(fù)雜的算法。這種算法也考量引用計(jì)數(shù),但是還考量其他一些東西。據(jù)說這種算法能夠判斷出循環(huán)引用(就是A引用B,B引用C,C再引用A)。總之SUN是鼓吹這種算法要比其他算法優(yōu)秀。我相信這種算法能夠提高程序的穩(wěn)定性。不過這種算法的一個(gè)很大的問題是內(nèi)存回收效率很差。java的一道經(jīng)典考題是這樣的:

for (int i=0;i<10;i++) {
??? ClassA a = new ClassA();
}

問執(zhí)行完以后,內(nèi)存里有幾個(gè)ClassA的對(duì)象實(shí)例。答案是10個(gè)。為什么,因?yàn)楦鶕?jù)java神秘的垃圾回收算法,這些超出作用域的,不再有人引用的對(duì)象實(shí)例并不急著銷毀。這在平時(shí)不會(huì)造成什么問題,但是如果在循環(huán)里創(chuàng)建了占用很大內(nèi)存的對(duì)象,不能及時(shí)回收就會(huì)造成堆內(nèi)存耗盡,溢出。java提供了System.gc()來讓你建議虛擬機(jī)回收內(nèi)存,但僅僅是建議,不是強(qiáng)制。

java虛擬機(jī)啟動(dòng)的時(shí)候會(huì)先向操作系統(tǒng)請(qǐng)求一小塊內(nèi)存作為堆內(nèi)存。隨著在堆里存放的數(shù)據(jù)(對(duì)象)越來越多,占用內(nèi)存超過一定百分比的時(shí)候,java虛擬機(jī)就會(huì)向操作系統(tǒng)請(qǐng)求更多的內(nèi)存分配給堆。我們?cè)诔绦蚶锟梢允褂?span style="FONT-WEIGHT: bold">Runtime.getRuntime().freeMemory()和Runtime.getRuntime().totalMemory()來獲得當(dāng)前堆的剩余內(nèi)存和總內(nèi)存。java虛擬機(jī)向操作系統(tǒng)請(qǐng)求的內(nèi)存是不會(huì)歸還的。還好它不會(huì)無限制地請(qǐng)求內(nèi)存,有一個(gè)上限值。在jre1.5里,這個(gè)上限值缺省是64M。你可以通過虛擬機(jī)參數(shù) -Xmx 來設(shè)置這個(gè)值。比如下面我設(shè)置這個(gè)值為640M:
java -Xmx640m org.formalin14.SampleClass

最近我在做批處理大量xml的時(shí)候,在每個(gè)循環(huán)里處理一個(gè)文件。每個(gè)循環(huán)里都要做很多運(yùn)算和轉(zhuǎn)換,同時(shí)也需要?jiǎng)?chuàng)建很多對(duì)象來保存中間數(shù)據(jù)。程序總是在處理了1000多個(gè)文件以后就報(bào)java.lang.OutOfMemoryError錯(cuò)誤,是堆內(nèi)存不夠用了。我檢查程序并確保循環(huán)里每個(gè)創(chuàng)建的對(duì)象在用完后都置為null(其實(shí)沒有必要),并且優(yōu)化了算法和程序結(jié)構(gòu),還在每次循環(huán)末尾加上System.gc()。但是在循環(huán)里總是不可避免地要?jiǎng)?chuàng)建新對(duì)象。而且,對(duì)于java虛擬機(jī),你沒有任何辦法強(qiáng)制它銷毀對(duì)象回收內(nèi)存。這樣,64M的堆內(nèi)存絕對(duì)是不夠它搗騰的。沒辦法只好把堆上限改到640M,總算夠它搗騰了。

總的來說,平時(shí)還是應(yīng)該養(yǎng)成良好的編程習(xí)慣,盡量節(jié)約內(nèi)存和CPU,建議把堆上限調(diào)到8M,強(qiáng)制自己寫高質(zhì)量的代碼。其他一些技巧比如避免讀取整個(gè)文件到內(nèi)存,讀一點(diǎn)處理一點(diǎn),然后就扔掉(祈禱扔掉以后能被趕快回收)。用SAX處理xml。用StringBuilder構(gòu)造字符串。多用大量的數(shù)據(jù)做測(cè)試,能看出程序是否真的優(yōu)秀。對(duì)于執(zhí)行快的循環(huán),末尾要加Thread.sleep(1),不然CPU一下就上去了。我前面的這個(gè)程序還是優(yōu)化得不夠,如果真有時(shí)間的話,相信任何程序都是可以優(yōu)化得很好的。不過不管怎么努力,java程序的效率怎么也比不上用c寫的程序。