-
GC的处理流程
- GC暂停进程中的所有线程。
- GC遍历堆中的所有对象,将某个位(这个位包含在对象同步块索引的字段中)设置为0(0表示将被删除)。
- GC检查所有活动根(根是指引用类型的变量),把活动根指向的对象标记为1,再次该对象引用的其它对象标记为1,依次类推。
- GC将活动对象进行重新排列,让所有幸存对象在内存中紧挨在一起。
- 将移动过的幸存对象的根更新固定的偏移量。
- GC恢复进程中的所有线程。
-
GC对活动对象(活动根)的认定:
- 被任何静态对象或静态字段引用。
- 应用程序栈中引用类型的变量指向的对象。
- 应用程序中的本地引用类型变量。
- 应用程序方法中的引用类型参数。
- 等待被终结(finalized)的对象。
-
GC将活动对象进行重新排列后获得的好处:
- 减少活动对象的内存地址范围,提升访问活动对象的性能。
- 让剩余的内存空间变成连续的,便于后续分配给新对象。
- 解决原生堆(非托管堆)的内存碎片问题。
-
GC代的算法的出发点:
- 对象越新,生存期越短。
- 对象越老,生存期越长。
- 回收堆的一部分,速度快于回收整个堆。
-
GC关于代的算法流程:
- 初始化堆,接下来分配的所有对象都是0代。
- 某个时间点0代超过预算,触发一次GC回收。
- 幸存下来的对象从0代提升到1代,此时0代不包含任何对象。
- 对1代对象进行紧凑排列。
- 在0代为新对象分配内存。
- 一段时间后再次触发GC对0代进行回收。
- 0代回收结束后发现0代的剩余空间不够用,此时会触发对1代的内存进行GC操作。
- 1代中幸存下来的对象会被提升到2代。
- 对1代对象进行紧凑排列。
- 某个时间后再次触发GC对0代和1代进行回收。
- 回收结束后发现0代的剩余空间不够用,此时会触发对2代的内存进行GC操作。
- 对1代和2代对象进行紧凑排列。
- 某个时间后GC对0-2代回收完成后,发现1代和2代的剩余空间太多,此时会对0-2代的内存进行重新分配。
-
GC触发的时机:
- 0代超过预算时。
- 显式调用System.GC.Collect()
- Windows报告低内存
- AppDomain正在被卸载
- CLR正常关闭时
-
GC与Finalize(终结)方法
- Finalize方法用于释放非托管资源的场景。
- 定义过Finalize方法的对象在分配内存时,会被附加到支持终结的对象列表中。
- GC在标记对象为可删除时会检查支持终结的对象列表是否存在,如果存在则会移到待终结的对象列表中。
- 待终结的对象以及它引用的对象在判断为待删除时,这些对象还是会被临时性地提升一个代,让这些对象存活一会来确保终结操作能正常执行。
- GC利用一个高优先级的线程来检查待终结的对象列表并调用对象的Finalize方法,Finalize方法完成后将对象引用从该列表中删除。
- 下一轮GC触发时刚被终结的对象以及它引用的对象会被正常回收。
-
GC与大对象
- ≥85KB的对象为大对象。
- 大对象被分配在独立的堆地址空间中。
- 大对象总是2代,并且只在2代回收操作时触发回收。
- 大对象一般不会做紧凑操作。
- 参考资料:
- 《CLR via C# Fourth Edition》 Jeffrey Richter
- Fundamentals of garbage collection
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals -
C# Garbage Collection Active Roots
https://stackoverflow.com/questions/8345075/c-sharp-garbage-collection-active-roots