將長生命周期對象和大對象池化 請記住最開始說的原則:對象要麼立即回收要麼一直存在。它們要麼在0代被回收,要麼在2代里一直存在。有些對象本質是靜態的,生命周期從它們被創建開始,到程式停止才會結束。其它對象顯然不需要永遠存在下去,但他們的生命周期會存在程式的某些上下文里。它們的存活時間會超過0代(1代) ...
將長生命周期對象和大對象池化
請記住最開始說的原則:對象要麼立即回收要麼一直存在。它們要麼在0代被回收,要麼在2代里一直存在。有些對象本質是靜態的,生命周期從它們被創建開始,到程式停止才會結束。其它對象顯然不需要永遠存在下去,但他們的生命周期會存在程式的某些上下文里。它們的存活時間會超過0代(1代)回收。這些類型的對象可以作為池化對象的備選。這雖然需要你手動管理記憶體,但實際情況下這是一個很好的選擇。另外一個重要的需要池化的對象是分配在LOH里的大對象。
沒有一個單一的標準方案或者API來實現對象的池化。 這需要你根據你的程式和對象的類型來設計對應方案。
對於如何管理池化對象,你可以將其當做非托管資源(記憶體)來進行管理。.NET對於這類資源有一個種管理模式:IDisposable。在本章前面我們介紹瞭如何實現這種模式。一個比較合理的方式是實現IDisposable介面,在Dispose方法里將對象丟回對象池。
實現一個好的對象池策略並不簡單,他取決於你程式要如何使用,以及那種類型的對象需要進行池化。
下麵的慄子,實現了一個簡單的對象池,你可以從裡面知道對象池會涉及那些內容。這個代碼可以從 PooledObjects 的慄子工程里看到。
interface IPoolableObject : IDisposable
{
int Size { get; }
void Reset();
void SetPoolManager(PoolManager poolManager);
}
internal class PoolManager
{
private class Pool
{
public int PooledSize { get; set; }
public int Count
{
get { return this.Stack.Count; }
}
public Stack<IPoolableObject> Stack { get; private set; }
public Pool()
{
this.Stack = new Stack<IPoolableObject>();
}
}
private const int MaxSizePerType = 10*(1 << 10); // 10 MB
private Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>();
public int TotalCount
{
get
{
int sum = 0;
foreach (var pool in this.pools.Values)
{
sum += pool.Count;
}
return sum;
}
}
public T GetObject<T>() where T : class, IPoolableObject, new()
{
Pool pool;
T valueToReturn = null;
if (pools.TryGetValue(typeof (T), out pool))
{
if (pool.Stack.Count > 0)
{
valueToReturn = pool.Stack.Pop() as T;
}
}
if (valueToReturn == null)
{
valueToReturn = new T();
}
valueToReturn.SetPoolManager(this);
return valueToReturn;
}
public void ReturnObject<T>(T value) where T : class, IPoolableObject, new()
{
Pool pool;
if (!pools.TryGetValue(typeof (T), out pool))
{
pool = new Pool();
pools[typeof (T)] = pool;
}
if (value.Size + pool.PooledSize < MaxSizePerType)
{
pool.PooledSize += value.Size;
value.Reset();
pool.Stack.Push(value);
}
}
}
internal class MyObject : IPoolableObject
{
private PoolManager poolManager;
public byte[] Data { get; set; }
public int UsableLength { get; set; }
public int Size
{
get { return Data != null ? Data.Length : 0; }
}
void IPoolableObject.Reset()
{
UsableLength = 0;
}
void IPoolableObject.SetPoolManager(PoolManager poolManager)
{
this.poolManager = poolManager;
}
public void Dispose()
{
this.poolManager.ReturnObject(this);
}
}
強制讓每個對象都實現介面會麻煩一些,但它除了方便外,還有一個重要的事實:為了使對象池重用對象,你必須能完全理解並控制它們。每次對象回到對象池前,你的代碼需要將對象重新設置到一個移植的,安全的狀態。這意味著你不應該天真的直接用第三方的對象池組件。你需要設計介面,並讓對象實現該介面,用來處理每個對象獲取時的初始化過程。你還需要特別小心對.NET框架對象做池化。
特別需要註意的是用來做對象池的集合,因為它們的性質決定--你並不希望它們銷毀所存儲的數據(畢竟這是池的重點),但你需要一個可以表示可以為空和可用空間的集合。幸運的是,大多數集合類型都實現了長度和容量的參數。考慮到使用現有的.NET集合類型會存在風險,建議最好自己實現集合類型,並實現一些標準的集合介面(如:IList
請牢記,如果不清理對象池裡的數據,這等同於記憶體泄漏。你的對象池應該有一個邊界大小(無論是位元組數量或者對象的數量),一旦超過,它應該通知GC清理多餘的對象。理想情況下,你的對象池足夠大,可以正常操作而不回收對象,但也會造成GC在執行回收時暫停時間變長,對象池裡對象越多回收演算法耗時也越多。當然最重要的還是對象池能滿足你的需要。
我通常不會將對象池作為預設的解決方案。它作為一種通用機制,顯得很笨重以及容易出錯。但你可能會發現你的程式在某些類型上很適用對象池。在一個應用里分配了大量的LOH對象,我們調查後發現,可以將一個單一的對象池化就能解決99%的問題。這個就是MemoryStream,我們使用它來序列化網路傳輸數據。實際的實現不僅僅是將構建了一個MemoryStream的隊列,因為要避免記憶體碎片,有一些更複雜的設計,但從本質上來說還是將它池化。每次使用完MemoryStream對象,它都會被放入對象池裡。
下一篇:第二章 GC -- 減少大對象堆的碎片,在某些情況下強制執行完整GC,按需壓縮大對象堆,在GC前收到消息通知,使用弱引用緩存對象