第六條:消除過期的對象引用 1.當你從手工管理內存的語言,如C或者C++轉換到具有垃圾回收功能的語言的時候,程序員的工作會變得更加容易,因為當你用完了對象之后,他們會被自動回收。當你認為自己不需要在考慮內存管理的事情時,其實則不然: 2.參見Stack:消除過期的對象引用的一個例子,非泛型版本 * <pre> * 1.不嚴格的講,這段程序有一個內存泄露,隨著垃圾回收器活動的增加,或者由于內存占用的不斷增加,程序性能的降低會逐漸表現出來。 * 2.在極端的情況下,這種內存泄露會導致磁盤交換,Disk paging,甚至導致程序失敗OutOfMemoryError,不過這種失敗情形相對比較少見 * 3.如果一個棧先是增長然后再收縮,那么從棧中彈出來的對象將不會被當做垃圾回收,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因為棧內部維護著這些對象的過期引用obsolete reference.所謂的過期引用,是指永遠也不會再被解除的引用。在本例中,凡是elements數組中的活動部分 * active portion之外的任何引用都是過期的。活動部分是指elements中下標小于size的那些元素.因為被彈出棧的元素,即大于size的且依然保存在elements * 數組中的不會被回收. * </pre> 3.在支持垃圾回收的語言中,內存泄露是很隱藏的。稱該類內存泄露為無意識的對象保持unintentional object retention更為恰當。如果一個對象引用被無意識的保留起來,那么垃圾回收機制不僅不會處理這個對象,而且也不會處理被這個對象所引用的所有其他對象。即使只有少量的幾個對象引用被無意識的保留下來,也會有許許多多的地方唄排除在垃圾回收機制之外,從而對性能造成潛在的重大影響。 4.修復方法很簡單:一旦對象引用已經過期,只需清空這些引用即可。對于該例,只要一個單元被彈出棧指向它的引用就過期了。 5.清空過期引用的另一個好處是,如果他們以后又被錯誤的解除引用,程序就會立刻拋出NullPointerException異常,而不是悄悄的錯誤運行下去。盡快的檢測出程序中的錯誤總是有益的。 6.清空對象引用應該是一種例外,而不是一種規范行為。消除過期引用最好的辦法是讓包含該引用的變量結束其生命周期。如果你是最緊湊的作用域范圍內定義一個變量,這種情形就會自然而然的發生。 7.Stack之所以易于遭受內存泄露的影響,問題在于Stack自己管理內存manage its own memory.存儲池storage pool包含了elments數組,對象引用單元而不是對象本身。的元素。數組活動區域中的元素是已分配的,而數組其余部分的元素則是自由的。但是垃圾回收期并不知道這一點,對于其而言,elements數組中的所有對象引用都同等有效。只有程序員知道數組的非活動部分是不重要的,程序員可以把這個情況告訴垃圾回收器。做法很簡單,一旦數組元素變成了非活動部分的一部分,程序員就手動清空這些數組元素。一般而言,只要類時自己管理內存,程序員就應該警惕內存泄露問題。一旦元素被釋放掉,則該元素中包含的任何對象引用都 應該被清空。 8.內存泄露的另一個常見來源是緩存。一旦你把對象放到緩存中,它就很容易被遺忘掉,而從使得他不再有用之后很長一段時間內仍然留在緩存中。對于這個問題,有幾種可能解決的方案。如果你正要實現這樣的緩存:只要在緩存之外存在對某個項的鍵的引用,該項就有意義。那么就可以用WeakHashMap代表緩存,當緩存中的項過期之后,他們就會自動被刪除。記住只有當所要的緩存項的生命周期是由該鍵的外部引用而不是用值決定時,WeakHashMap才有用處。 9.更為常見的情形則是:緩存的聲明周期是否有意義并不是很容易確定。隨著時間的推移,其中的項會變得越來越沒有價值。在這種情況下,緩存應該時不時的清除沒用的項。這項清除工作可以由一個后臺線程可能是Timer或者ScheduledThreadPoolExecutor來完成或者也可以給緩存添加新條目的時候順便清理。LinkedHashMap類利用它的removeElderestEntry方法可以很容易的實現后一種方案.(注:LRU),對于更加復雜的緩存,必須直接使用java.lang.ref. 10.內存泄露的第三個常見來源是監聽器和其他回調。如果你實現了一個API,client在這個API中注冊回調,卻沒有顯示的取消注冊。那么除非你采取某些動作,否則他們就會積聚。確保回調立即被當做垃圾回收的最佳方式是只保存他們的弱引用weak reference。例如,至將他們保存成WeakHashMap中的鍵。 11.由于內存泄露通常不會表現為明顯的失敗,所以他們可以在一個系統中存很多年。往往只有通過仔細檢查代碼或者借助于Heap剖析工具Heap Profiler才能發現內存泄露問題。因此,如果能夠在內存泄露之前就知道如何預測此問題的話并阻止他們發生,那最好不過部分源碼:
package com.book.chap2.clearOverdueRef;

import java.util.Arrays;
import java.util.EmptyStackException;


/** *//**
*
* 消除過期的對象引用的一個例子,非泛型版本
* <pre>
* 1.不嚴格的講,這段程序有一個內存泄露,隨著垃圾回收器活動的增加,或者由于內存占用的不斷增加,程序性能的降低會逐漸表現出來。
* 2.在極端的情況下,這種內存泄露會導致磁盤交換,Disk paging,甚至導致程序失敗OutOfMemoryError,不過這種失敗情形相對比較少見
* 3.如果一個棧先是增長然后再收縮,那么從棧中彈出來的對象將不會被當做垃圾回收,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因為棧內部
* 維護著這些對象的過期引用obsolete reference.所謂的過期引用,是指永遠也不會再被解除的引用。在本例中,凡是elements數組中的活動部分
* active portion之外的任何引用都是過期的。活動部分是指elements中下標小于size的那些元素.因為被彈出棧的元素,即大于size的且依然保存在elements
* 數組中的不會被回收.
* </pre>
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-3-12
*
*/

public class Stack


{
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 10;
public Stack()

{
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

/** *//**
* 入棧
* @param e
*/
public void push(Object e)

{
ensureCapacity();
elements[size++] = e;
}

/** *//**
* 彈棧
* @return
*/
public Object pop()

{
if(size == 0)

{
throw new EmptyStackException();
}
return elements[--size];
}

/** *//**
* 修復pop:一旦對象引用已經過期,只需清空這些引用即可。對于該例,只要一個單元被彈出棧指向它的引用就過期了。
* @return
*/
public Object gcPop()

{
if(size == 0)

{
throw new EmptyStackException();
}
Object result = elements[--size];
//置null,移除過期引用
elements[size] = null;
return result;
}

/** *//**
* 超出空間后,擴展空間
*/
public void e
nsureCapacity()

{
if(elements.length == size)

{
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}

posted on 2013-03-15 19:15
landon 閱讀(1841)
評論(0) 編輯 收藏 所屬分類:
Program 、
Book