带着问题去思考!大家好
今天我们继续优化。
避免对象固定
对象固定(Pinning)是为了能够安全地将托管内存的引用传递给本机代码,最常见的用处就是 传递数组和字符串。如果不与本机代码进行交互,就完全不应该有对象固定的需求。
对象固定会把内存的地址固定下来, 垃圾回收器就无法移动这些对象,会增加内存碎片的可能,垃圾回收器会记住那些被固定的对象,以便能利用固定对象之间的空闲内存。过多的情况,还会导致内存碎片的产生和内存堆的扩大
对象固定既可能是显式的,也可能是隐式的,使用GCHandleType.Pinned类型的GCHandle或者fixed关键字,可以完成显式对象固定,代码块必须标记为unsafe。用fixed/using用起来更方便,(fixed和GCHandle之间的区别类似于using和显式调用Dispose的差别),异步环境下是无法使用的,因为异步状态不能传递handle,也不能在回调方法中销毁handle。
避免使用终结方法
非必要情况下,永远不要实现终结方法(Finalizer).终结方法是一段由垃圾回收器引发调用的代码,用于清理非托管资源。终结方法由一个独立的线程调用,排成队列依次完成,而且只有依次垃圾回收之后,对象被垃圾回收器声明已销毁,才会进行调用。如果类实现了终结方法,对象就一定会滞留在内存中,即便在垃圾回收时应该被销毁的情况下。
如果实现了终结方法,那就必须同时实现IDisposable接口以启用显示清理,还要在Dispise方法中调用GC.SuppressFinalize(this)来吧对象从移除终结队列中移除。只要能在下次垃圾回收之前调用Dispose,就可以适时把对象清理干净。
class Foo:IDisposeable { Foo(){Dispose(false);} public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if(disposing) { this.manageResoure.Dispose(); } //清理非托管资源 UnsafeClose(this.handle); } }
避免分配大对象
大对象的界限被为85000字节,任何大于这个值的对象都被认为是大对象,并独立的内存堆中进行分配,尽量避免,不仅是因为LOH的垃圾回收开销大,更多原因是因为内存碎片会导致内存用量不断增长
避免缓冲区复制
任何时候都应该避免复制数据,比如你已经把文件数据读入了MemorySteam(如果需要较大的缓存区,最好是用池化的流),一旦内存分配完毕,就应把此MemoryStream视为只读流,所有需要访问的MemoryStream的组件都能从同一份数据备份中读取数据,
如果需要表示整个缓冲区的一段,请使用ArraySegment<T>类,可用来代表底层byte[]类型缓冲区的一部分区域。此ArraySegment可以传给API,而与原来的流无关,甚至可以被绑定到一个新的MemoryStream对象上,这些过程都不会发生数据复制。
var memoryStream=new MemoryStream(); var segment=new ArraySegment<byte>(memoryStream.GetBuffer(),100,1024); .... var blockStream=new MemoryStream(segment.Array,segment.Offset,segment.Count);
内存复制造成的最大影响肯定不是CPU,而是垃圾回收。
对长期存活对象和大型对象进行池化
对象要么转瞬即逝,要么一直存活;要么在第0代垃圾回收时消失,要么就在第2代内存堆中一直留下去。有些对象基本上是静态的,伴随程序自然诞生,并在程序生存期间保持存活,还有一些对象看不出有一直存活的必要。但它们在程序上下文中体现出来的生存期,决定了它们会历经第0代(或者1代)垃圾回收并仍然存活。这时候应该考虑对这类对象进行池化。还有LOH中分配的对象,典型例子就是集合类对象,
.NET已提供了一种针对受限托管资源的处理模式--IDisposable模式。比较合理的设计就是派生一个新类型并实现IDisposable接口,在Dispose方法中将池化对象归还共享池(Pool)。
public interface IPoolableObject:IDisposable { int Size { get; } void Reset(); void SetPoolManager(PoolManager poolManager); } public 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>(); } } const int MaxSizePerType = 10 * (1 << 10);//10MB 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); } } 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); } } }
被池化对象必须要实现自定义接口,需要有点工作量。为了实现池化和对象重用,必须完全了解并掌握这些对象。因为在每次把池化对象归还共享池时,你的代码必须把对象重置为已知的,安全的状态,
共享池中的对象永远不会被销毁,这与内存泄漏难以区分开。通常不要把池化作为默认解决方案,主要处理一些LOH分配。能消除99%的LOH问题。这类对象就是MemorySteam,我们用它来序列化并通过网络传递数据。
减少LOH的碎片整理
如果做不到完全避免LOH,就尽力避免碎片整理,
保证LOH的每次分配都是同一尺寸的,或者至少有几种标准尺寸的组合。比如LOH的一种常规用途就是缓冲区池,不能让各个缓冲区大小不易,而应该所有缓冲区都是相同尺寸,或者固定大小(1MB),如果某一块缓冲区确实需要被垃圾回收了,那么下一次分配的缓冲区就有很大概率会落在这块空闲内存上。而不会放到堆的末尾去。
来源:https://www.cnblogs.com/ccaa/p/12578552.html