当 CLR 进行垃圾回收时,它也会压缩堆。现在,如果 CLR 不使用连续的内存块,它就无法摆脱中间未使用的空间并将对象压在一起,对吗?
但如果这是真的,那么就会出现另一个问题。如果 CLR 运行的进程需要的内存超过了 CLR 当前占用的内存,那么 CLR 如何确保新占用的内存与 CLR 当前保留的内存连续?可能是内存已被非 clr 进程使用。因此,可用内存可能与 CLR 已经占用的内存不连续。所以现在 CLR 在两个不连续的内存块之间工作,在这种情况下,压缩将如何工作?
当 CLR 进行垃圾回收时,它也会压缩堆。现在,如果 CLR 不使用连续的内存块,它就无法摆脱中间未使用的空间并将对象压在一起,对吗?
但如果这是真的,那么就会出现另一个问题。如果 CLR 运行的进程需要的内存超过了 CLR 当前占用的内存,那么 CLR 如何确保新占用的内存与 CLR 当前保留的内存连续?可能是内存已被非 clr 进程使用。因此,可用内存可能与 CLR 已经占用的内存不连续。所以现在 CLR 在两个不连续的内存块之间工作,在这种情况下,压缩将如何工作?
这不是它的工作原理。CLR 以大块的形式分配虚拟内存空间,称为segments,底层的winapi调用是VirtualAlloc()。它分配的最小段是 2 兆字节。如果程序快速消耗内存,它会要求更大的。
GC 仅压缩段的内容。不需要使段本身在地址空间中是连续的。这也不行,CLR 不仅要处理 GC 数据使用的地址空间,还要处理代码和其他堆使用的地址空间。包括为非托管代码和数据进行的分配。一个典型的托管程序至少有 10 个不同的堆。其中之一是大对象堆,GC 用于未压缩的大型对象,因为移动它们的成本太高。只有小于 85,000 字节的对象最终会进入分代堆并被压缩。
压缩对于避免碎片和提高 L1 缓存使用率很有用。一个片段不能大于 85,000 字节,L1 数据缓存通常为 32,768 字节。两者都远低于最小的段大小。
“可能是内存已被非 clr 进程使用。”
CLR 旨在在现代计算机上运行。它们具有虚拟地址空间,即每个进程都有自己的连续地址空间。内存可能是物理交错的,但不是逻辑交错的。
尽管如此,分配的内存可能是碎片化的。这没什么大不了的。首先,有两个堆(小对象/大对象),小对象堆分为 3 代。所以无论如何你都不需要一个单一的分配。
压缩不会保持顺序。如果有一个 16 字节的空洞,那么将所有对象向下移动 16 字节就太昂贵了。取而代之的是,可以从任何地方将单个对象移入孔中-理想情况下是从未使用的页面中移入,以便可以回收。