我不知道 D 的细节,但是在 Java 和 .net 中,每个类对象都包含有关其类型的信息,并且还包含有关它是否是任何监视器锁的目标、是否有资格进行终结清理以及各种其他事情。拥有所有对象存储此类信息的标准方法可以使语言和/或框架的用户和实现者在许多事情上更加方便。顺便说一句,在 .net 的 32 位版本中,每个对象的开销为 8 个字节,但最小对象大小为 12 个字节。这个最小值源于这样一个事实,即当垃圾收集器移动对象时,
编辑
如果你想使用一个类,因为你需要能够持久化对数据项的引用,空间是非常宝贵的,并且你的使用模式是这样的,你会知道数据项何时仍然有用以及何时过时,您可以定义一个结构数组,然后将索引传递给数组元素。可以编写代码来非常有效地处理这个问题,而且开销基本上为零,前提是您的程序结构允许您确保每个被分配的项目都只被释放一次,并且一旦被释放就不会被使用。
如果您无法轻易确定对对象的最后一个引用何时超出范围,那么 8 个字节将是一个非常合理的开销水平。我希望大多数框架会强制对象在 32 位边界上对齐(所以我很惊讶添加一个字节会将大小推到九而不是十二)。如果一个系统将有一个比 Commodore 64(*) 工作得更好的垃圾收集器,那么每个对象需要绝对最小的一点开销来指示哪些东西被使用,哪些东西没有被使用。此外,除非想要为可以包含补充信息的对象和不能包含补充信息的对象设置单独的堆,否则每个对象要么包含用于补充信息指针的空间,要么包含用于所有补充信息的空间(锁定、放弃通知请求等)。虽然在某些情况下为两类对象设置单独的堆可能是有益的,但我怀疑这些好处通常会证明增加的复杂性是合理的。
(*) Commodore 64 垃圾收集器通过从内存顶部向下分配字符串来工作,而变量(未经过 GC 处理)是自下而上分配的。当内存已满时,系统将扫描所有变量以查找对存储在最高地址的字符串的引用。然后该字符串将被移动到内存的最顶部,并且对它的所有引用都将被更新。然后,系统将扫描所有变量以在它刚刚移动的地址下方的最高地址处找到对字符串的引用,并更新对该字符串的所有引用。该过程将重复,直到找不到更多要移动的字符串为止。该算法不需要将任何额外的数据与字符串一起存储在内存中,但它当然很慢。Commodore 128 垃圾收集器与 GC 空间中的每个字符串一起存储一个指向变量的指针,该变量包含一个引用和一个长度字节,可用于在 GC 空间中查找下一个较低的字符串;因此,它可以检查每个字符串以确定它是否仍在使用,如果是,则将其重新定位到内存的顶部。快得多,但代价是每个字符串需要三个字节的开销。