带着问题去思考!大家好
今天我们继续优化。
避免对象固定
对象固定(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
