.NET对象的内存布局

这篇具有很好参考价值的文章主要介绍了.NET对象的内存布局。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在.NET中,理解对象的内存布局是非常重要的,这将帮助我们更好地理解.NET的运行机制和优化代码,本文将介绍.NET中的对象内存布局。
.NET中的数据类型主要分为两类,值类型和引用类型。值类型包括了基本类型(如int、bool、double、char等)、枚举类型(enum)、结构体类型(struct),它们直接存储值。引用类型则包括了类(class)、接口(interface)、委托(delegate)、数组(array)等,它们存储的是值的引用(数据在内存中的地址)。

值类型的内存布局

值类型的内存布局是顺序的,并且是紧凑的。例如,定义的结构体SampleStruct,其中包含了四个int类型字段,每个字段占用4个字节,因此整个SampleStruct结构体在内存中占用16个字节。

public struct SampleStruct
{
    public int Value1; 
    public int Value2;
    public int Value3;
    public int Value4;
}

它在内存中的布局如下:

引用类型的内存布局

引用类型的内存布局则更为复杂。首先,每个对象都有一个对象头,其中包含了同步块索引和类型句柄等信息。同步块索引用于支持线程同步,类型句柄则指向该对象的类型元数据。然后,每个字段都按照它们在源代码中的顺序进行存储。

例如,下面的类:

public class SampleStruct
{
    public int Value1; 
    public int Value2;
    public int Value3;
    public int Value4;
}

它在内存中的布局如下:

在.NET中,每个对象都包含一个对象头(Object Header)和一个方法表(Method Table)。

  • 对象头:存储了对象的元信息,如类型信息、哈希码、GC信息和同步块索引等。对象头的大小是固定的,无论对象的大小如何,对象头都只占用8字节(在64位系统中)或4字节(在32位系统中)。
  • 方法表:这是.NET用于存储对象的类型信息和方法元数据的数据结构。每个对象的类型,包括其类名、父类、接口、方法等都会被存储在MethodTable中。

在32位系统中,对象头和方法表指针各占4字节,再加上至少4字节的实例字段空间(即使没有实例字段,也会分配这部分空间以满足内存对齐的要求),因此每个对象至少占用12字节的空间。在64位系统中,由于指针的大小是8字节(但只有后4个字节被使用),64位系统中需要8字节来保证内存对齐,每个对象至少占用24字节的空间。

每个.NET对象的头部都包含一个指向同步块的索引(Sync Block Index)和一个指向类型的指针(Type Pointer)。

  • Sync Block Index: 是一个指向同步块的索引。同步块用于存储对象锁定和线程同步信息的结构。当你对一个对象使用lock关键字或Monitor类进行同步时,会用到同步块。如果对象未被锁定,那么这个索引通常是0。
  • Type Pointer: 是一个指向对象类型MethodTable的指针。

字段按照源代码中的顺序存储。值类型的字段直接存储值,引用类型的字段存储的是对值的引用,即指针。在32位系统中,指针占用4个字节,而在64位系统中,指针占用8个字节。可以通过StructLayoutAttribute来自定义.NET中的对象内存布局。例如,通过Sequential参数可以保证字段的内存布局顺序与源代码中的相同,或者通过Explicit参数来手动指定每个字段的偏移量。实例成员需要8字节对齐,即使没有任何成员,也需要8个字节。

堆上分配对象的最小占用空间

// The generational GC requires that every object be at least 12 bytes in size.
#define MIN_OBJECT_SIZE     (2*TARGET_POINTER_SIZE + OBJHEADER_SIZE)

进阶

在.NET中,对象在内存中的布局是由运行时环境自动管理的。而对于结构体,我们可以通过System.Runtime.InteropServices命名空间的StructLayout属性来设置其在内存中的布局方式。

  • LayoutKind.Auto:这是类和结构的默认布局方式。在这种方式下,运行时会自动选择合适的布局。
  • LayoutKind.Sequential:在这种方式下,字段在内存中的顺序将严格按照它们在代码中的声明顺序。
  • LayoutKind.Explicit:这种方式允许你显式定义每个字段在内存中的偏移量。

以下是一个例子,它定义了一个名为SampleStruct的结构体,并使用了StructLayout属性来设置其布局方式。

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SampleStruct
{
    public byte X;
    public double Y;
    public int Z;
}

在这个例子中,我们可以使用ObjectLayoutInspector库来查看SampleStruct在内存中的布局。

void Main()
{
	TypeLayout.PrintLayout<SampleStruct>();
}

上述代码的输出如下,值得注意的是,使用System.Runtime.InteropServices命名空间的StructLayout属性将结构的布局设置为Sequential。这意味着在内存中结构的布局是按照在结构中声明的字段的顺序进行的。

Type layout for 'SampleStruct'
Size: 24 bytes. Paddings: 11 bytes (%45 of empty space)
|===========================|
|     0: Byte X (1 byte)    |
|---------------------------|
|   1-7: padding (7 bytes)  |
|---------------------------|
|  8-15: Double Y (8 bytes) |
|---------------------------|
| 16-19: Int32 Z (4 bytes)  |
|---------------------------|
| 20-23: padding (4 bytes)  |
|===========================|

这里,我们可以看到SampleStruct在内存中的具体布局:首先是X字段(占用1个字节),然后是7个字节的填充,接着是Y字段(占用8个字节),然后是Z字段(占用4个字节),最后是4个字节的填充。总共占用24个字节,其中11个字节是填充。

这个例子中,我们将结构体SampleStruct的布局设置为Auto。在这种方式下,运行时环境会自动进行布局,可能会对字段进行重新排序,或在字段之间添加填充以使他们与内存边界对齐。

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Auto)]
public struct SampleStruct
{
    public byte X;
    public double Y;
    public int Z;
}

如下所示再来检查SampleStruct在内存中的布局:

Type layout for 'SampleStruct'
Size: 16 bytes. Paddings: 3 bytes (%18 of empty space)
|===========================|
|   0-7: Double Y (8 bytes) |
|---------------------------|
|  8-11: Int32 Z (4 bytes)  |
|---------------------------|
|    12: Byte X (1 byte)    |
|---------------------------|
| 13-15: padding (3 bytes)  |
|===========================|

从输出结果可以看出,运行时环境对字段进行了重新排序,并在字段之间添加了填充。首先是Y字段(占用8个字节),然后是Z字段(占用4个字节),接着是X字段(占用1个字节),最后是3个字节的填充。总共占用16个字节,其中3个字节是填充。这种布局方式有效地减少了填充带来的空间浪费,并可能提高内存访问效率。文章来源地址https://www.toymoban.com/news/detail-638576.html

到了这里,关于.NET对象的内存布局的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++对象封装后的内存布局

    在C语言中,数据和数据的处理操作(函数)是分开声明的,在语言层面并没有支持数据和函数的内在关联性,我们称之为过程式编程范式或者程序性编程范式。C++兼容了C语言,当然也支持这种编程范式。但C++更主要的特点在支持基于对象(object-based, OB)和面向对象(object

    2024年04月15日
    浏览(39)
  • 一看就懂的java对象内存布局

    前言 Java 中一切皆对象,同时对象也是 Java 编程中接触最多的概念,深入理解 Java 对象能够更帮助我们深入地掌握 Java 技术栈。在这篇文章里,我们将从内存的视角,带你深入理解 Java 对象在虚拟机中的表现形式。 学习路线图: 1. 对象在哪里分配? 在 Java 虚拟机中,Java 堆

    2024年02月05日
    浏览(40)
  • java JUC并发编程 第九章 对象内存布局与对象头

    第一章 java JUC并发编程 Future: link 第二章 java JUC并发编程 多线程锁: link 第三章 java JUC并发编程 中断机制: link 第四章 java JUC并发编程 java内存模型JMM: link 第五章 java JUC并发编程 volatile与JMM: link 第六章 java JUC并发编程 CAS: link 第七章 java JUC并发编程 原子操作类增强: link 第八章

    2024年02月07日
    浏览(40)
  • 【JUC进阶】03. Java对象头和内存布局

    为了后面更好的学习锁优化以及运作过程,需要我们对HotSpot虚拟机的Java对象内存布局有一定的了解,也作为技术储备。 在HotSpot虚拟机中,对象在堆内存中存储的布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。 而数组对象和普通对

    2024年02月10日
    浏览(40)
  • 通过读取字节内容分析对象在内存中的布局

    在《如何计算一个实例占用多少内存?》中我们知道一个值类型或者引用类型的实例在内存中占多少字节。如果我们知道这段连续的字节序列的初始地址,我们就能够将代表该实例的字节内容读取出来。在接下来的内容中,我们将利用一个简单的方法输出指定实例的字节序列

    2024年02月08日
    浏览(36)
  • JVM面试题-JVM对象的创建过程、内存分配、内存布局、访问定位等问题详解

    内存分配的两种方式 指针碰撞 适用场合:堆内存 规整 (即没有内存碎片)的情况下。 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。 使用该分配方式的GC收集器:

    2024年02月08日
    浏览(49)
  • Net 高级调试之十一:托管堆布局架构和对象分配机制

    一、简介 今天是《Net 高级调试》的第十一篇文章,这篇文章来的有点晚,因为,最近比较忙,就没时间写文章了。现在终于有点时间,继续开始我们这个系列。这篇文章我们主要介绍托管堆的架构,对象的分配机制,我们如何查找在托管堆上的对象,我学完这章,很多以前

    2024年02月05日
    浏览(37)
  • 【Java虚拟机学习2】HotSpot虚拟机下对象的创建及在Java堆中对象的内存分配、布局和对象的访问

    对象的生命周期主要分为创建、使用、销毁这三大阶段。从它被创建的一刻开始,到它被垃圾收集器(Garbage Collector)回收的一刻结束 对象的创建 。包括:1、类的加载 2、内存的分配 3、初始化零值 4、设置对象头 5、执行init方法(具体操作步骤请看上述内容,其中步骤1的类

    2024年02月16日
    浏览(40)
  • 【JVM】Java堆 :深入理解内存中的对象世界

    人不走空                                                                            目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 ​编辑 什么是Java堆? 作用和特点 1. 存储对象实例 2. 垃圾收集 3. 对象

    2024年01月19日
    浏览(50)
  • Advanced .Net Debugging 4:基本调试任务(对象检查:内存、值类型、引用类型、数组和异常的转储)

    一、介绍 这是我的《 Advanced .Net Debugging 》这个系列的第四篇文章。今天这篇文章的标题虽然叫做“基本调试任务”,但是这章的内容还是挺多的。由于内容太多,故原书的第三章内容我分两篇文章来写。上一篇我们了解了一些调试技巧,比如:单步调试、下断点、过程调试

    2024年03月09日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包