條款7:消除廢棄的對(duì)象引用(一)
如果你從??需要?動(dòng)進(jìn)?內(nèi)存管理的語?(如C或是C++)轉(zhuǎn)到了垃圾回收語?(如Java),那么作為程序員來說,你的?作會(huì)簡(jiǎn)化很多,因?yàn)閷?duì)象在被使?完畢后會(huì)?動(dòng)回收。在初次經(jīng)歷這個(gè)事情時(shí),?切感覺都像夢(mèng)幻?樣。這很容易會(huì)讓你覺得不需要考慮內(nèi)存管理問題,不過事實(shí)卻并?如此。
考慮如下簡(jiǎn)單的棧實(shí)現(xiàn)代碼:
public class Stack {
private Object[] elements;
private int size = 0;
/*棧初始容量大小*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/*構(gòu)造方法,初始化數(shù)組*/
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
/**
* 確保當(dāng)棧滿時(shí),擴(kuò)展容量為原來的一倍
*/
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
上述程序并沒有明顯的錯(cuò)誤(不過請(qǐng)查看條款29來了解更加通?的版本)。你可以不斷測(cè)試該程序,程序也會(huì)順利通過每個(gè)測(cè)試,不過有?個(gè)潛伏的問題。?致來說,該程序存在?處『內(nèi)存泄露』,其性能會(huì)逐步降低,這是因?yàn)椴粩嘣黾拥睦占骰顒?dòng)與內(nèi)存占?問題。在極端情況下,這種內(nèi)存泄露會(huì)導(dǎo)致磁盤分?,甚?會(huì)因OutOfMemoryError造成程序失敗,不過這種失敗的情況是?常少?的。
那么內(nèi)存泄露出現(xiàn)在什么地?呢?如果棧不斷增?,然后再收縮,那么從棧中彈出的對(duì)象就不會(huì)被垃圾回收掉,即便使?了棧的程序不再引?他們亦如此。這是因?yàn)闂>S護(hù)了對(duì)這些對(duì)象的廢棄的引?。廢棄的引?指的是永遠(yuǎn)不會(huì)被解引?的引?。在該示例中,位于元素?cái)?shù)組『活動(dòng)部分』之外的任何引?都是廢棄的?;顒?dòng)部分包含了索引?于size的元素。
垃圾收集語?中的內(nèi)存泄露(更恰當(dāng)?shù)慕蟹ㄊ?strong>?意的對(duì)象保持)是?常不易察覺的。如果對(duì)象引?被?意保持了,那么不僅該對(duì)象會(huì)從垃圾收集中排除出去,該對(duì)象所引?的其他對(duì)象也會(huì)被排除出去,以此類推。即便只有少量的對(duì)象引?被?意保持了,造成的后果就是會(huì)有很多、很多對(duì)象會(huì)從垃圾收集中排除出去,這會(huì)對(duì)性能造成很嚴(yán)重的影響。
這類問題的解決?案很簡(jiǎn)單:?旦引?變成廢棄狀態(tài),?刻將其置為null。對(duì)于我們的Stack類來說,如果元素從棧中彈出,那么對(duì)其的引?就變成廢棄狀態(tài)了。pop?法的正確版本如下代碼所示:
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
//置空,強(qiáng)制讓GC去回收廢棄的引用對(duì)象
elements[size] = null;
return result;
}
將廢棄引?置為null的另?個(gè)好處在于,如果他們隨后被錯(cuò)誤地解引?了,那么程序就會(huì)?刻失敗,并拋出NullPointerException異常,?不是安靜地做錯(cuò)誤的事情。盡可能快地檢測(cè)程序錯(cuò)誤終歸是有好處的。