从 API 角度来看,Mono 正紧紧跟随着.NET 的脚步大踏步前进着,但有个关键领域 Mono 却落后了。Mono 中默认的垃圾收集器采用的是可移植、但却不太精确的 Boehm-Demers-Weiser 保守式垃圾收集器。Boehm 垃圾收集器的主要问题在于无法精确读取寄存器与栈帧。因为无法确定给定值到底是指针还是标量,因此它总是假设给定值是指针,并且将相关联的对象标记为存活状态。这么做不仅会错误导致大块内存无法分配,同时还使得压缩可用空间这项工作变得异常艰难。
SGen(Simple Generational)是 Mono 新一代的垃圾收集器。见名知意,这个持续了两年的项目正尝试替代 Mono 原来的垃圾收集器,它使用精确的分代式(generational)垃圾收集器,类似于.NET 版本的 CLR。SGen 垃圾收集器使用两生代而非.NET 中的三个,但像.NET 一样对于大对象使用独立的堆。
在 Mono 2.10 之前(现在仍处于预览版),SGen 仍旧是保守的。新版本增加了对托管栈帧的精确收集的支持,这样基本就不会遇到误报的问题了。来自于 p/invoke 调用的非托管栈帧仍旧使用保守的方式扫描。
类似于.NET,SGen 最大的缺陷在于固定对象。如果将对象固定在 SGen 中(想想.NET 中的 0 生代),那么它就无法彻底清除,这会导致内存碎片。由于几个原因,SGen 的这个问题甚至更糟。你不仅需要处理碎片,而且 SGen 需要从这些碎片中分配内存。理想情况下,所有的活动对象都应从 SGen 中复制出来,并且得到重用。
为了解决这些问题,SGen 并不仅仅像.NET 那样检测显示的固定对象。如前所述,对非托管栈帧的保守式扫描会导致对象的固定,这是因为栈中的数字值可能碰巧与内存地址一样。据推测,随着与 p/invoke 调用相关的逻辑变得更加可靠,这将在未来的版本中得到修复。
评论