垃圾回收工作機制 學習自《Java程序員 上班那點事兒》
4.5 內(nèi)存垃圾回收問題
那本譚浩強主編的Java入門教材說:
……
1、簡單性
設(shè)計Java語言的出發(fā)點就是容易編程,不需要深奧的知識。Java語言的風格十分接近C++語言,但要比C++簡單得多。Java舍棄了一些不常用的、難以理解的、容易混淆的成分,如運算符重載、多繼承等。增加了自動垃圾搜集功能,用于回收不再使用的內(nèi)存區(qū)域。這不但使程序易于編寫,而且大大減少了由于內(nèi)存分配而引發(fā)的問題。
……
這樣類似的描述出現(xiàn)在眾多的Java入門級教材中,非常容易讓人們忽略了內(nèi)存垃圾回收的問題,其實Java的垃圾回收問還是需要關(guān)注一下的。這個問題在招聘單位的筆試題中出現(xiàn)的頻率也比較高,我們需要好好的研究一下Java的垃圾回收機制。
4.5.1 什么是內(nèi)存垃圾,哪些內(nèi)存符合垃圾的標準
我們在前面講過了,堆是一個"運行時"數(shù)據(jù)區(qū),是通過"new"等指令建立的,Java的堆是由Java的垃圾回收機制來負責處理的,堆是動態(tài)分配內(nèi)存大小,垃圾收集器可以自動回收不再使用的內(nèi)存空間。
也就是說,所謂的"內(nèi)存垃圾"是指在堆上開辟的內(nèi)存空間在不用的時候就變成了"垃圾"。
C++或其他程序設(shè)計語言中,必須由程序員自行聲明產(chǎn)生和回收,否則其中的資源將消耗,造成資源的浪費甚至死機。但手工回收內(nèi)存往往是一項復雜而艱巨的工作。因為要預先確定占用的內(nèi)存空間是否應該被回收是非常困難的!如果一段程序不能回收內(nèi)存空間,而且在程序運行時系統(tǒng)中又沒有了可以分配的內(nèi)存空間時,這段程序就只能崩潰。
Java和C++相比的優(yōu)勢在于,這部分"垃圾"可以被Java 虛擬機(JVM)中的一個程序發(fā)現(xiàn)并自動清除掉,而不用程序員自己想著"delete"了。
Java語言提供了一個系統(tǒng)級的線程,即垃圾收集器線程(Garbage Collection Thread),來跟蹤每一塊分配出去的內(nèi)存空間,當JVM處于空閑循環(huán)時,自動回收每一塊可以回收的內(nèi)存。
4.5.1.1 垃圾回收工作機制
垃圾收集器線程它是一種低優(yōu)先級的線程,它必須在一個Java程序的運行過程中出現(xiàn)內(nèi)存空閑的時候才去進行回收處理。
垃圾收集器系統(tǒng)有其判斷內(nèi)存塊是否需要回收的判斷標準的。垃圾收集器完全是自動被執(zhí)行的,它不能被強制執(zhí)行,即使程序員能明確地判斷出某一塊內(nèi)存應該被回收了,也不能強制執(zhí)行垃圾回收程序進行垃圾回收。
程序員可以做的只有調(diào)用"System.gc()"來"建議"執(zhí)行垃圾收集器程序,但是這個垃圾收集程序什么時候被執(zhí)行以及是否被執(zhí)行了,都是不不能控制的。但是雖然垃圾收集器是低優(yōu)先級的線程,卻在系統(tǒng)內(nèi)存可用量過低時,它仍然可能會突發(fā)地執(zhí)行來挽救系統(tǒng)。
4.5.1.2 哪些符合"垃圾"標準
如果想了解JVM的垃圾回收,就必須要知道JVM垃圾回收的標準。
垃圾收集器的"垃圾"標準:對象已經(jīng)不能被程序中的其他程序所引用的時候,那么這個對象的內(nèi)存空間已經(jīng)沒有用了。
比如當一個方法執(zhí)行完畢時,在這個方法中聲明的對象就超出其聲明周期,這時候就可以被當作垃圾收集了,只有當這個方法被再次被調(diào)用時才會被重新創(chuàng)建。
例如:
…… |
另外還可以將對象的引用變量初始化為null值,也可以來暗示垃圾收集器來收集該對象。
例如:
…… |
finalize()在該對象垃圾回收前調(diào)用
垃圾收集器跟蹤每一個對象,把那些不可到達的對象占有的內(nèi)存空間收集起來,并且在每次進行垃圾收集之前,垃圾收集器都會調(diào)用一下finalize()方法。Java語言允許程序員給任何對象添加finalize( )方法,但也不能過分依賴該方法對系統(tǒng)資源的回收和再利用,因為這個方法調(diào)用后的執(zhí)行結(jié)果是不可預知的。對于任何給定對象,Java 虛擬機最多只調(diào)用一次 finalize 方法。
我們用這個程序來演示一下:
public class finalizeTest{ |
java -Xmx1k finalizeTest |

這時候,我們將JVM所許可使用的最大內(nèi)存設(shè)置成"1k",當內(nèi)存被占滿前JVM會首先去進行內(nèi)存回收,于是失去活動的"test"對象被回收,在回收前調(diào)用了"finalize()"。
4.5.2 JVM垃圾回收的相關(guān)知識
JVM使用的是分代垃圾回收的方式,主要是因為在程序運行的時候會有如下特點:
大多數(shù)對象在創(chuàng)建后很快就沒有對象使用它了。
大多數(shù)在一直被使用的對象很少再去引用新創(chuàng)建的對象。
因此就將Java對象分為"年輕"對象和"年老"對象,JVM將內(nèi)存堆(Heap)分為兩個區(qū)域,一個是"年輕"區(qū),另一個是"老"區(qū),Java將這兩個區(qū)域分別稱作是"新生代"和"老生代"。
"新生代"區(qū)域中,絕大多數(shù)新創(chuàng)建的對象都存放在這個區(qū)域里,此區(qū)域一般來說較小而且垃圾回收頻率較高,同時因為"新生代"采用的算法和其存放的對象的特點,使該區(qū)域垃圾回收的效率也非常高。
而"老生代"區(qū)域中存放的是在"新生代"中生存了較長時間的對象,這些對象將被轉(zhuǎn)移到"老生代"區(qū)。這個區(qū)域一般要大一些而且增長的速度相對于"新生代"要慢一些,"老生代"垃圾回收的執(zhí)行頻率也會低很多。
由于JVM在垃圾回收處理時會消耗一定的系統(tǒng)資源,因此有時候通過JVM啟動的時候添加相關(guān)參數(shù)來控制"新生代"區(qū)域的大小,來調(diào)整垃圾回收處理的頻率非常有用。以便于我們更合理的利用系統(tǒng)資源。
"新生代"區(qū)域設(shè)置參數(shù)是"-Xmn",用這個參數(shù)可以制定"新生代"區(qū)域的大小。
我們來舉一個例子說明:
我們就用系統(tǒng)自帶的程序作為例子,在命令行上鍵入如下指令:
CD C:"java"demo"jfc"SwingSet2[回車] |
[GC [DefNew: 3469K->84K(3712K), 0.0007778 secs] |
我們需要解釋一下輸出的詳細內(nèi)容的意思,拿第一行輸出來說:
"DefNew: 3469K->84K(3712K), 0.0007778 secs"是指"新生代"的垃圾回收情況,這里的意思是從占用3469K內(nèi)存空間變?yōu)?4K內(nèi)存空間,用時0.0007778秒。
"23035K->19679K(28728K), 0.0009191 secs"是指總體GC的回收情況,整體堆空間占用從23035K降低到19679K的水平,用時0.0009191秒。
那么,這時候我們在將"新生代"的內(nèi)存設(shè)為8M,并把堆的最大可控值設(shè)定為32M,再去執(zhí)行,鍵入如下指令:
java -jar -verbose:gc -Xmn8m -Xmx32m |
[GC [DefNew: 6633K->6633K(7424K), 0.0000684 secs] 25496K->18505K(32000K), 0.0934295 secs] |
這個結(jié)果說明:
"[DefNew: 6633K->6633K(7424K), 0.0000684
secs]"是指"新生代"的垃圾回收情況,這里的意思是從占用6633K內(nèi)存空間變?yōu)?633K內(nèi)存空間,用時0.
0000684秒。
"25374K->18820K(32000K), 0.0639274
secs"是指總體GC的回收情況,整體堆空間占用從25374K降低到18820K的水平,用時0. 0639274秒。
"[Tenured:
18740K->18820K(24576K), 0.0636505
secs]"是指"老生代"GC的回收情況,整體堆空間占用從18740K降低到18820K的水平,用時0.0009012秒。
通過這些參數(shù)的調(diào)整我們可以看到在處理垃圾收集問題時,從垃圾回收的頻率是時間方面的變化,我們可以根據(jù)不同程序的不同情況予以調(diào)整。
最后有必要提一下GC的相關(guān)參數(shù):
-XX:+PrintGCDetails
顯示GC的詳細信息
-XX:+PrintGCApplicationConcurrentTime
打印應用執(zhí)行的時間
-XX:+PrintGCApplicationStoppedTime
打印應用被暫停的時間
注:":"后的"+"號表示開啟此選項,如果是"-"號那么表示關(guān)閉此選項。
posted on 2009-07-07 13:23 星期五 閱讀(377) 評論(0) 編輯 收藏 所屬分類: JAVA SE