一、托管堆内存分配过程
A | B | C | NextObjPtr |
---|---|---|---|
--- | -- | ---- | -------------------- |
- 计算需要的内存大小
- 类型本身大小
- CLR对象需要的开销
- 型对象指针
- 同步块索引
- 检查NextObjPtr指向的空间是否足够
- 足够
- NextObjPtr处放置对象
- 清空分配的内存
- NextObjPtr赋值给this
- NextObjPtr移动到下一个可用位置
- 不够,进行一次GC
- 暂停所有线程
- 标记堆中所有对象为可删除
- 从栈和静态变量出发,遍历所有对象
- 销毁堆中剩余可删除对象
- 整理内存碎片,修改所有相关的引用指针
- 修改NextObjPtr
- GC后还是不够
- 抛出OutOfMemoryException
- 足够
二、分代GC:提升性能
CLR提供0-2一共三个堆(逻辑上的),每个堆的大小有预算且是动态的。
分代GC基于如下假设
- 对象越新,生存期越长
- 对象越老,生存期越短
- 回收堆的一部分,速度比回收整个堆更快
新建一个对象的过程的lua伪代码
heap0, heap1, heap2
function Alloc(size)
if heap0.avaliSpace < size then
--对0号堆进行一次GC
local activeObjs = heap0:Collect()
-- 0号中堆剩下的对象紧凑排列后移入1号堆
heap1:AddObjs(activeObjs)
if heap1.avaliSpace < 0 then
-- 对1号堆进行一次GC
activeObjs = heap1:Collect()
-- 1号堆剩下的对象紧凑排列后移入2号堆
heap2:AddObjs(activeObjs)
if heap2.avaliSpace < 0 then
-- 回收二代堆,压缩存活对象
heap2:Collect()
end
end
end
if heal0.avaliSpace >= size then
return heap0:Alloc(size)
else
error('no enough space')
else
end
在这一过程中,可能会动态调整各堆的预算。
三、大对象
- 大对象属于二代堆
- 大对象和小对象不在一起分配
- 大对象在GC后不会压缩
四、GC回收模式
- 客户端(低延迟),服务器(低资源占用)
- 并发(默认,有一个GC线程平时在收集不可达对象),非并发
五、回收不受托管堆管理的资源
- 在Finalize重写方法中定义
- 在对象真正释放才会调用
- 由独立线程运行
- 可能造成阻塞或死锁,无法解决
- 建议不要使用
- 使用
SafeHandle
包装native resource handle
使用IDisposable
接口控制本地资源生存期
- 如果类的一个字段实现了
IDisposable
,那么类也需要实现,在Dispose
方法中调用字段的Dispose
方法 - 实现IDisposable接口的类中其他的方法和get property,建议在native resource释放后调用会抛出
ObjectDisposedException
- 如果没有调用Dispose,记得在Finalize中释放本地资源
- 不建议对
Dispose
方法实现线程安全,而是在调用处保证没有其他线程在同时调用 - 正确使用
Dispose
方法的方式- 在
finally
代码块中调用Dispose
var fs = new FileStream("temp.dat", FileMode.Create); try { fs.Write(bytes, 0, bytes.Length); } finally { if (fs != null) fs.Dispose(); }
- 使用
using
简化代码,两者是等价的using (var fs = new FileStream("temp.dat", FileMode.Create) { fs.Write(bytes, 0, bytes.Length); }
- 在
FileStream
与StreamWriter
FileStream
包含一个缓冲区对象StreamWriter
也包含一个缓冲区对象StreamWriter.Dispose
对调用绑定FileStream
的Dispose
方法StreamWriter
的Finalize
函数中不再向FileStream
中写入缓存的数据,会造成数据的丢失
Finalize的调用过程
当GC到拥有Finalize方法的对象时
- 移动到一个特殊的队列(对象又被引用住了)
- 提升到下一代堆
- 调用Finalize方法
- 等待下一次GC再回收这些对象
通过GCHandle类监控和控制对象的生命周期
public static GCHandle GCHandle.Alloc(object value, GCHandleType type);
- 使对象在内存中固定位置(不会GC和内存整理),调用Native函数
var obj = new byte[1000]; var handle = GCHandle.Alloc(obj, GCHandleType.Pinned); SomeNativeMethod(handle.ToIntPtr()); // 异步使用完毕后,调用Free handle.Free();
- 使用P/Invoke时,会自动为参数固定住内存
- 可以使用
fixed
关键字进行内存固定(尝试了下发现必须在unsafe代码块中使用)var bytes = new byte[1000] fixed(byte* pBytes = bytes) { SomeNativeMethod((IntPtr)pBytes); }
- 弱引用住一个对象
var obj = new object(); var handle = GCHandle.Alloc(obj, GCHandleType.Weak); var obj = handle.Target;
- 可以使用
WeakReference
简化使用方法
- 可以使用