【问题标题】:What does the .NET CLR do with the memory overhead in empty types?.NET CLR 如何处理空类型的内存开销?
【发布时间】:2014-11-15 23:39:26
【问题描述】:

所有 .NET 类都将同步块和类型指针存储为它们的实例的一部分。这些在 32 位进程中总共占用 8 个字节,在 64 位进程中占用 16 个字节。但是,空类型实例的对象大小分别为 12 和 24 字节。

我看到一些文章说这是对齐问题,但是由于同步块和类型句柄是指针大小的,我不明白为什么需要添加任何填充。

其他文章说垃圾收集器需要它,但是它对开销有什么作用?它不能在那里存储任何东西,因为如果类型具有实例字段,则实际上会使用额外的空间。垃圾收集器是否在对象终结后的某个时间和在它被释放之前对该内存做一些事情,它需要一个地方来放置一些东西(可能是一个指针)?

以下是我读过的一些关于空字体大小的文章:

Performance Considerations of Class Design and General Coding in .NET:

如果您创建了一个没有字段的对象并在调试器中查看它,您会注意到它的大小实际上是 12 个字节,而不是 8 个。对于 64 位进程,该对象将是 24 个字节。这是因为最小尺寸是基于对齐的。值得庆幸的是,这个“额外”的 4 字节空间将被一个字段使用。

Of memory and strings:

“最小”大小分别为 12 字节和 24 字节。换句话说,你不能有一个只是开销的类型。请注意“Empty”类如何占用与创建 Object 实例相同的大小……实际上有一些空闲空间,因为 CLR 不喜欢对没有数据的对象进行操作。

Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects:

如前所述,当前的 GC 实现需要一个至少 12 字节的对象实例。如果一个类没有定义任何实例字段,它将携带 4 个字节的开销。剩下的 8 个字节将被 Object Header(可能包含一个 syncblk 编号)和 TypeHandle 占用。

【问题讨论】:

  • “空类型实例的对象大小为 12 和 24 字节” - 你在某处读过这个吗?
  • 对齐是一个非常重要的处理器实现细节。它提供了 .NET 内存模型的基本保证。原子性很重要,尤其是对于对象引用。没有它,你就无法编写线程代码。
  • @dcastro 我编辑了我的问题,包括对我读过的一些关于空字体大小的文章的引用。
  • @HansPassant 但是类型/同步块指针的 8 和 16 字节已经对齐。我不确定 12 和 24 字节大小如何有助于对齐。
  • 对。但是 8 已经是 4 的倍数。而 16 已经是 8 的倍数。

标签: .net clr


【解决方案1】:

啊,我明白了。

数组将其长度存储为其实例字段数据的前 4 或 8 个字节(分别在 32 位和 64 位系统中)(在类型指针之后)。为了获得内存使用的大小,CLR 从方法表(由类型指针指向)中获取基本大小,并将长度乘以每个项目的大小(它也从方法表中获取)。

换句话说,公式是:

内存大小 = 基本大小 + 长度 * 项目大小

CLR 的实现者不想为数组使用一个公式,而对其他类型使用不同的公式,而是希望为这两种公式提供一个公式,从而在获取内存大小时无需任何条件逻辑。

但是这怎么可能呢?其他对象类型不在其实例字段数据的前 4 或 8 个字节中存储长度。

关键是项目大小,存储在方法表中。对于非数组类型,项目大小为 0。这意味着无论实例字段数据的前 4 或 8 个字节中存储的值如何,长度 * 项目大小将始终为 0,并且该公式将起作用。

但即使实例字段数据的前 4 或 8 字节的值无关紧要,仍然需要分配它以防止访问违规。

感谢 Hans 向我指出 SetFree 方法。当我看到 CLR 本质上是将任意对象转换为一维字节数组时,我意识到它假设一切都可以以这种方式进行转换,并且对原因的调查让我得到了这个答案。

【讨论】:

  • 我很好奇为什么这个答案被否决了。在我看来,OP 在研究这个问题方面做得非常出色。
【解决方案2】:

是的,即使是一个空类也需要 4/8 字节用于通常存储对象字段的对象部分。所以一个完全空的类在 32 位模式下仍然需要 4 + 4 + 4 = 12 个字节,在 64 位模式下需要 8 + 8 + 8 = 24 个字节。当对象存在时,根本不会使用这 4/8 个额外字节。

释放对象时需要此存储空间。然后它可以成为堆段的空闲块列表的一部分。如果堆段包含固定对象并且无法完美压缩,则会发生这种情况。在这种情况下,同步块在调试版本中设置为 -1,类型句柄设置为内部假 FreeObject 类型。并且对象大小占用 4 个字节。

SSCLI20 source code,clr/src/vm/gcsmp.cpp文件,SetFree()函数可见。

【讨论】:

  • 感谢您向我指出 SetFree 函数!查看该方法非常有用,并让我意识到为什么需要额外的内存。看起来额外的字节不仅在对象转换为 FreeObject 类型后才严格需要。但它可以转换为那种类型,因为最小大小要求意味着总会有一个长度字段的空间,而 FreeObject 类型基本上是一维字节数组。
猜你喜欢
  • 2010-09-06
  • 1970-01-01
  • 2011-06-09
  • 2016-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-03
  • 1970-01-01
相关资源
最近更新 更多