.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字节,因此每个对象至少占用12字节的空间(不包括对象的实例字段)。在64位系统中,由于指针的大小是8字节,但只有后4个字节被使用,每个对象至少占用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个字节是填充。这种布局方式有效地减少了填充带来的空间浪费,并可能提高内存访问效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/69926.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Gradio:交互式Python数据应用程序的新前沿

一、说明 什么是Gradio以及如何使用Gradio在Python中创建DataApp或Web界面&#xff1f;使用 Gradio 将您的 Python 数据科学项目转换为交互式应用程序。 摄影&#xff1a;Elijah Merrell on Unsplash Gradio是一个Python库&#xff0c;允许我们快速为机器学习模型创建可定制的接…

ChatGPT 的“自定义”功能对免费用户开放,在问题信息不足情况下还会反问来获取必要信息...

“ ChatGPT推出‘自定义’功能并向免费用户开放。即使信息有限&#xff0c;系统也能巧妙地通过反问获取必要细节&#xff0c;进一步提升了用户体验和互动效果。” 01 — 近期 ChatGPT 官方可能也发现绝大多数人用不好 Prompt 提示词&#xff0c;无法发挥彻底发挥大模型的优势&a…

wsl2安装docker引擎(Install Docker Engine on Debian)

安装 1.卸载旧版本 在安装 Docker 引擎之前&#xff0c;您必须首先确保卸载任何冲突的软件包。 发行版维护者在他们的存储库。必须先卸载这些软件包&#xff0c;然后才能安装 Docker 引擎的正式版本。 要卸载的非官方软件包是&#xff1a; docker.iodocker-composedocker-…

网神 SecGate 3600 防火墙任意文件上传漏洞复现(HW0day)

0x01 产品简介 网神SecGate3600下一代极速防火墙&#xff08;NSG系列&#xff09;是基于完全自主研发、经受市场检验的成熟稳定网神第三代SecOS操作系统 并且在专业防火墙、VPN、IPS的多年产品经验积累基础上精心研发的高性能下一代防火墙 专门为运营商、政府、军队、教育、大型…

【网络】高级IO

目录 一、五种IO模型 1、阻塞IO 2、非阻塞IO 3、信号驱动 4、IO多路转接 5、异步IO 6、总结 二、高级IO重要概念 1、同步通信与异步通信 2、阻塞 vs 非阻塞 三、非阻塞IO 1、fcntl 2、实现函数SetNoBlock 四、IO多路转接select 1、select 1.1、参数解释 1.2、…

Unity 编辑器资源导入处理函数 OnPostprocessAudio :深入解析与实用案例

Unity 编辑器资源导入处理函数 OnPostprocessAudio 用法 点击封面跳转下载页面 简介 在Unity中&#xff0c;我们可以使用编辑器资源导入处理函数&#xff08;OnPostprocessAudio&#xff09;来自定义处理音频资源的导入过程。这个函数是继承自AssetPostprocessor类的&#xff…

前后端分离------后端创建笔记(上)

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

在Centos环境中搭建Nginx环境

一、Nginx概念简介 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 Nginx与redis相同&#xff0c;都是基于多路复用模型构建出的产物&#xff0c;因此它与R…

Redis心跳检测

在命令传播阶段&#xff0c;从服务器默认会以每秒一次的频率&#xff0c;向主服务器发送命令&#xff1a; REPLCON FACK <rep1 ication_ offset>其中replication_offset是从服务器当前的复制偏移量。 发送REPLCONF ACK命令对于主从服务器有三个作用&#xff1a; 检测主…

Arcgis中直接通过sde更新sqlserver空间数据库失败

问题 背景 不知道有没有人经历过这样一个情况,我们直接在Arcgis中通过sde更新serserver数据库会失败,就是虽然在sde更新sqlserver数据库,但是在Navicat中通过sql语句来查询,发现数据并没有更新,如:上图中,更新数据库后,第一张图是sde打开的sqlserver数据库,它的数据库…

solr迁移到另一个solr中(docker单机)

背景介绍 solr数据迁移&#xff0c;或者版本升级&#xff0c;需要用到迁移&#xff0c;此处记录一下迁移方法以及过程中遇到的问题。我这边使用的是docker环境&#xff0c;非docker部署的应该也是一样的。 solr部署教程 准备工作 ● solrA 版本&#xff1a; 8.11.2 (已有so…

使用 Packet Tracer 查看协议数据单元

练习 2.6.2&#xff1a;使用 Packet Tracer 查看协议数据单元 地址表 本练习不包括地址表。 拓扑图 学习目标 捕获从 PC 命令提示符发出的 ping运行模拟并捕获通信研究捕获的通信从 PC 使用 URL 捕获 Web 请求运行模拟并捕获通信研究捕获的通信 简介&#xff1a; Wiresha…

提升Element UI分页查询用户体验与交互:实现修改未保存提示

我实现的功能是在 element ui 的分页组件中进行分页查询时&#xff0c;如果当前有未保存的修改数据就提示用户&#xff0c;用户可以选择是否放弃未保存的数据。确认放弃就重新查询数据&#xff1b;选择不放弃&#xff0c;不重新查询&#xff0c;并且显示条数选择框保持原样&…

轻装上阵,不调用jar包,用C#写SM4加密算法【卸载IKVM 】

前言 记得之前写了一个文章&#xff0c;是关于java和c#加密不一致导致需要使用ikvm的方式来进行数据加密&#xff0c;主要是ikvm把打包后的jar包打成dll包&#xff0c;然后Nuget引入ikvm&#xff0c;从而实现算法的统一&#xff0c;这几天闲来无事&#xff0c;网上找了一下加密…

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测 目录 时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-BiGRU-Attention时间序列预测&#xff0c;CNN-BiGRU-Attention结合注意力机制时…

uni-app实现图片上传功能

效果 代码 <uni-forms-item name"ViolationImg" label"三违照片 :"><uni-file-picker ref"image" limit"1" title"" fileMediatype"image" :listStyles"listStyles" :value"filePathsL…

UML-类图和对象图

目录 类图概述&#xff1a; 1.类: 2.属性: 3.类的表示&#xff1a; 4.五种方法: 类图的关系&#xff1a; 1.关联 2.聚合 3.组合 4.依赖 5.泛化 6.实现 对象图概述&#xff1a; 1. 对象图包含元素: 2. 什么是对象 3.对象的状态可以改变: 4.对象的行为 5.对象标…

ad+硬件每日学习十个知识点(30)23.8.10 (SDIO端口扩展器TXS02612RTWR,模数转换器ADC121C027)

文章目录 1.cpu->SDIO端口扩展器->SD卡槽->SD卡(当然也可以反向读取)2.SDIO端口扩展器介绍3.SDIO端口扩展器TXS02612RTWR4.SD卡槽5.什么是模数转换器&#xff1f;6.I2C模数转换器ADC121C0277.模数转换方案 1.cpu->SDIO端口扩展器->SD卡槽->SD卡(当然也可以反…

【JPCS出版】第五届能源、电力与电网国际学术会议(ICEPG 2023)

第五届能源、电力与电网国际学术会议&#xff08;ICEPG 2023&#xff09; 2023 5th International Conference on Energy, Power and Grid 最近几年&#xff0c;不少代表委员把目光投向能源电力领域&#xff0c;对促进新能源发电产业健康发展、电力绿色低碳发展&#xff0c;提…

Kubernetes(K8s)从入门到精通系列之十:使用 kubeadm 创建一个高可用 etcd 集群

Kubernetes K8s从入门到精通系列之十&#xff1a;使用 kubeadm 创建一个高可用 etcd 集群 一、etcd高可用拓扑选项1.堆叠&#xff08;Stacked&#xff09;etcd 拓扑2.外部 etcd 拓扑 二、准备工作三、建立集群1.将 kubelet 配置为 etcd 的服务管理器。2.为 kubeadm 创建配置文件…