本文讲述了 .NET GC 的一些细节知识,内容大部分来自于书籍 Under the Hood of .NET Memory Management (注:本文假设你了解 .NET 的基础知识,譬如值类型,引用类型等)
之前在讲述 GC 分代回收的时候,我们只是了解了一下 SOH(Small Object Heap) 相关的内存回收行为,实际上,在进行 Gen 2 GC(也称为 full GC)时, GC 流程同样会回收 LOH(Large Object Heap)的内存,只是在方式方法上, LOH 的内存回收和 SOH 的内存回收有很大不同.
首先, LOH 的内存分布比较"直白",并没有 SOH 中所谓的分代概念(Gen 0, Gen 1 和 Gen 2).
再者, 由于 LOH 存储的是大于等于 85000 字节的大对象,所以复制移动这些对象的成本很高,所以, LOH 也并不会进行内存压缩(SOH 对各个分代都会进行内存压缩).
在(大)对象的申请与释放(即 Gen 2 GC)过程中, LOH 会将各个可用的内存区域记录到一个被称为 Free Space Table 的表中:
在申请(大)对象时, .NET 会首先检查 Free Space Table,如果发现有足够的可用内存,则会将(大)对象分配到对应的可用内存区域中,并更新 Free Space Table,如果找不到足够的可用内存, .NET 会将(大)对象分配到当前的堆尾 (有可能会申请新的内存).
(图中 Object E 被分配到了堆尾,即 Object D 之上)
出于性能考虑, .NET 更偏向于将对象分配至堆尾(当然,这种偏向也会导致 LOH 出现更多的内存碎片)
另外值得一提的是, LOH 存储的对象其实也并不都大于等于 85000 字节,对于一些内部数组,譬如 double 数组,由于 1 个 double 占据 8 个字节,似乎 double 数组的长度要大于等于 10625(85000 / 8)(此处我们忽略 double 数组的一些额外存储消耗)才会存储于 LOH 中,但实际上,仍然是出于性能考虑,大于等于 1000 长度的 double 数组就会存储于 LOH 中(不同类型数组的相关阈值也有所不同).
未完待续(to be continued)