請記住最開始說的原則:對象要麼當即回收要麼一直存在。它們要麼在0代被回收,要麼在2代裏一直存在。有些對象本質是靜態的,生命週期從它們被建立開始,到程序中止纔會結束。其它對象顯然不須要永遠存在下去,但他們的生命週期會存在程序的某些上下文裏。它們的存活時間會超過0代(1代)回收。這些類型的對象能夠做爲池化對象的備選。這雖然須要你手動管理內存,但實際狀況下這是一個很好的選擇。另一個重要的須要池化的對象是分配在LOH裏的大對象。html
沒有一個單一的標準方案或者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前收到消息通知,使用弱引用緩存對象this