目录html
原文地址:https://devblogs.microsoft.com/premier-developer/managed-object-internals-part-1-layout/
原文做者:Sergey
译文做者:杰哥很忙git
托管对象本质1-布局
托管对象本质2-对象头布局和锁成本
托管对象本质3-托管数组结构
托管对象本质4-字段布局github
托管对象的布局很是简单:托管对象包含实例数据、指向元数据的指针(也称为方法表指针)和内部信息包(也称为对象头)。windows
译者补充: 方法表指针在某些文章也被称之为类型句柄,英文是TypeHandle。数组
当我第一次看到对象的布局时,我产生了一些疑问:缓存
译者补充:做者对于布局怪异实际指的就是对象头的偏移量为负数。因为托管对象以引用地址的偏移量记为0,对象头大小为4或8字节(取决因而32位仍是64位,实际64位对象头也仅使用4字节,前面4个字节填充0)。所以因为对象头在对象指针以前,所以它的偏移量位-4或-8。安全
当我开始思考布局并作了一个快速研究时,我只有几个选择:微信
JVM 从一开始就对托管对象使用了相似的布局。
今天听起来有点疯狂,但请记住,因为Java早就有了一个特性(又名数组协方差),C#在借鉴Java语言时也引用了这一有史以来最糟糕的特性。与这个决定相比,重用一些关于对象结构的想法听起来并不合理。架构
译者补充:数组协方差可能存在没法保证类型安全,从而产生一个运行时异常。详情能够看与C#数组的协方差和逆差app
对象头的大小能够增大,而在 CLR 中没有横切更改。
对象头包含 CLR 使用的一些辅助信息,CLR 可能须要比指针大小字段更多的信息。事实上,移动电话中使用的 .Net Compact Framework 对于大小对象具备不一样的头(有关详细信息,请参阅 WP7:CLR 托管对象开销)。桌面 CLR 从未使用过此功能,但这并不意味着未来不可能实现此功能。
译者补充:这里说的CLR没有根据切面大小改变对象头,指的是桌面CLR,由于移动设备的CLR会根据对象大小改变对象头的布局。
缓存行和其余性能相关特征。
Chris Brumme – CLR 架构师之一,在他的发表的Value Types的评论中提到,缓存友好性正是托管对象布局的缘由。从理论上讲,因为缓存行大小(64 字节),访问彼此较近的字段的效率可能更高。这意味着根据字段在对象中的位置不一样,访问间接引用字段会有不一样的性能差别。我花了一些时间试图证实对于现代处理器该理论依据仍然是成立的,但没法得到任何可以显示存在的差别基准测试数据。
花了一些时间试图验证个人理论后,我联系了Vance Morrison问这个问题,并获得了如下的答案:目前的设计没有特别考虑。
所以,对于"为何托管对象的布局如此怪异?"的一个简单回答是因为历史缘由形成的。老实说,我能够看到在负索引移动对象头的逻辑,以强调此数据块是 CLR 的实现细节,它的大小能够随时间而变化,而且不该由用户检查。
译者补充:原做者说的是因为历史缘由形成的也没有毛病,由于非托管代码就包含了对象头和对象引用地址,所以托管代码延续了这一风格。
如今是时候审视布局的更多细节了。再次以前,咱们思考一下,CLR能够与托管对象实例关联哪些额外信息?如下是一些想法:
除了实例状态以外,CLR还存储了许多与类型相关的信息,如方法表、接口映射、实例大小等等,但这与咱们当前的讨论无关。
托管对象头可用于多种不一样的用途。你可能认为垃圾收集器(GC)使用对象头中的一个位来标记该对象是由根引用的,而且应该保持活动状态。这是一种常见的误解,只有少许的名著说起。
好比Jeffrey Richter写的《CLR via C#》, 《Pro .NET Performance》做者是Sasha Goldstein,固然还有一些其余人.
CLR 做者决定不使用对象头,而是使用一个巧妙的技巧:方法表指针的最低位用于存储在垃圾回收期间存储对象可访问且不该被回收的标志。
下面是来自Coreclr的一个mark标记的实现,在文件gc.cpp的8974行:
#define marked(i) header(i) -> IsMmarked(); #define set_marked(i) header(i)->SetMarked() #define clear_marked(i) header(i)->ClearMarked() // class CObjectHeader BOOL IsMarked() const { return !!(((size_t)RawGetMethodTable()) & GC_MARKED); } void ClearMarked() { RawSetMethodTable(GetMethodTable()); } void SetMarked() { RawSetMethodTable((MethodTable*)(((size_t)RawGetMethodTable()) | GC_MARKED)); } MethodTable* GetMethodTable() const { return((MethodTable*)(((size_t)RawGetMethodTable()) & (~(GC_MARKED)))); }
因为gc.cpp文件太大致使GitHub不分析它。 这意味着我不能将超连接添加到特定代码行。
CLR 堆中的托管指针以4个或8个字节地址长度进行对齐,取决于32位仍是64位平台。这意味着每一个指针的 2 或 3 位始终为 0,可用于其余目的。JVM 也使用一样的技巧,称为"压缩 Oops",该功能容许 JVM 具备 32 GB 堆大小,而且仍使用 4 个字节做为托管指针。
译者补充:当咱们在对象上标注StructLayout以控制对象的分布甚至偏移值。若对象没有填满4字节或8字节时,CLR会进行自动填充。
“这意味着每一个指针的 2 或 3 位始终为 0,可用于其余目的。”对于这句话的解释,我的理解以下:因为64位指针最多支持2^64^内存,即16TiB的内存大小,而对于windows系统则有软件上的内存大小限制,windows7旗舰版支持192GB的内存,而windows server 2008 R2支持2TiB内存大小,Windows Server 2012提升到4TiB的最大内存限制。所以能够如做者所说,windows 64位操做系统预留了2到3位指针用于其余目的,所以最大内存支持4TiB。
从技术上讲,即便在 32 位平台上,也有 2 位可用于标志。基于 object.h 文件的注释,咱们能够认为确实如此,而且方法表指针的第二个最低位用于固定(以标记在垃圾回收的压缩阶段不该移动对象)。不幸的是,并不能判断该说法是否正确,由于来自 gc.cpp(行 3850-3859)的 SetPinned/IsPinned 方法基于对象头中的保留位实现,而且我没法在 coreclr 代码版本库中找到实际设置方法表指针位的任何代码。
下次咱们会讨论所得实现以及锁的性能消耗大小。
微信扫一扫二维码关注订阅号杰哥技术分享
出处:http://www.javashuo.com/article/p-bmfgoxgy-mm.html 做者:杰哥很忙 本文使用「CC BY 4.0」创做共享协议。欢迎转载,请在明显位置给出出处及连接。