.NET对象的内存布局

news2025/2/28 19:42:34

在.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.coloradmin.cn/o/862272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的事件总线EventBus服务功能

手把手教你如何开发一个属于自己的事件总线EventBus服务功能 事件总线 — EventBusEventBus的基本原理 总体架构图定义事件监听器EventListener接口方法 — topic方法 — onMessage 定义事件驱动传输模型定义事件监听器EventListenerRegistry接口initRegistryEventListener注册…

Oracle 知识篇+会话级全局临时表在不同连接模式中的表现

标签&#xff1a;会话级临时表、全局临时表、幻读释义&#xff1a;Oracle 全局临时表又叫GTT ★ 结论 ✔ 专用服务器模式&#xff1a;不同应用会话只能访问自己的数据 ✔ 共享服务器模式&#xff1a;不同应用会话只能访问自己的数据 ✔ 数据库驻留连接池模式&#xff1a;不同应…

风口来临,你真的了解什么是嵌入式吗?

什么是嵌入式&#xff1f; 书本上的定义是这样的&#xff1a; 嵌入式&#xff08;Embedded&#xff09;是指将计算机系统嵌入到被控制对象中的一种技术。它不同于传统的个人电脑或服务器&#xff0c;而是专门设计用来执行特定功能的计算机系统。嵌入式系统通常被用于控制、监测…

【1289. 下降路径最小和 II】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个 n x n 整数矩阵 grid &#xff0c;请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下降路径 定义为&#xff1a;从 grid 数组中的每一行选择一个数字&#xff0c;且按顺序选出来的数…

odoo 开发入门教程系列-模块交互

模块交互 在上一章中&#xff0c;我们使用继承来修改模块的行为。在我们的房地产场景中&#xff0c;我们希望更进一步&#xff0c;能够为客户生成发票。Odoo提供了一个开发票模块&#xff0c;因此直接从我们的房地产模块创建发票是很简单的&#xff0c;也就是说&#xff0c;一…

比较 Java 中的 ModelMapper 和 MapStruct:自动映射器的强大功能

了解如何在自动映射器 ModelMapper 和 MapStruct 之间进行选择&#xff0c;以提高生产力和可维护性&#xff0c;并减少数据映射中的错误。 在 Java 应用程序中&#xff0c; 数据映射 是一项常见任务&#xff0c;涉及将对象从一种类型转换为另一种类型。这个过程可能会变得复杂而…

浅谈Spring与字节码生成技术

概要 今天来谈一谈我们熟知的Spring框架和字节码技术有什么联系。 Java程序员几乎都了解Spring。 它的IoC&#xff08;依赖反转&#xff09;和AOP&#xff08;面向切面编程&#xff09;功能非常强大、易用。而它背后的字节码生成技术&#xff08;在运行时&#xff0c;根据需要…

Linu学习笔记——常用命令

Linux 常用命令全拼&#xff1a; Linux 常用命令全拼 | 菜鸟教程 一、切换root用户 1.给root用户设置密码 sudo passwd root 2.输入密码&#xff0c;并确认密码 3.切换到root用户 su&#xff1a;Swith user(切换用户) su root 二、切换目录 目录结构&#xff1a;Linux 系…

APP外包开发的H5开发框架

跨平台移动应用开发框架允许开发者使用一套代码在多个操作系统上构建应用程序&#xff0c;从而节省时间和资源。以下是一些常见的跨平台移动应用开发框架以及它们的特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0…

再论态势感知中的几个问题

1、态势中的状态信息与趋势信息有何不同 在分析和决策过程中&#xff0c;态势是指当前的情境、环境或状况。状态信息和趋势信息是对态势的不同方面进行描述和分析的方式。 状态信息是关于当前态势的实时或静态描述。它提供的是当前的数据、指标或状态的具体数值或描述。例如&am…

使用python-magic和wxPython识别文档类型

以下是一篇关于使用python-magic模块和wxPython库识别文档类型的相关博客文章的示例&#xff1a; 摘要&#xff1a; 本文介绍了如何使用python-magic模块和wxPython库创建一个简单的文件列表应用程序&#xff0c;该应用程序可以显示所选文件夹中文件的类型。 介绍&#xff1…

【三维重建】【深度学习】instant-nsr-pl代码Pytorch实现--训练自己的数据集

【三维重建】【深度学习】instant-nsr-pl代码Pytorch实现–训练自己的数据集 基于Instant-NGP的神经表面重建&#xff1a;该存储库包含 NeRF 和 NeuS 的简洁且可扩展的实现&#xff0c;用于基于 Instant-NGP 和 Pytorch-Lightning 框架的神经表面重建&#xff0c;旨在为基于 Ne…

ACM Journals的Word模板使用心得

按照说明一步一步按照顺序调整格式&#xff0c;体力活&#xff0c;考验耐心细心。 两个模板&#xff0c;第一个是 Submission Template投稿用的&#xff0c;第二个是Primary Article Template接收后用的。 及时保存备份&#xff0c;以便恢复到最初满意的状态。 格式确定后&a…

CSDN到底要多少积分才有排名(图解)

2016年8月16日的截图&#xff1a; 2016年8月17日的截图&#xff1a; CSDN的排名是完全按照积分排的&#xff0c;只隔了22分而已&#xff0c;千里之外和2万名的差别就是卡在这个地方。 2016年10月18日的截图&#xff1a; 这是刚刚进入前7000名&#xff0c;刚好访问也是刚刚突破4…

Linux系统调试课:Linux Kernel Printk

🚀返回专栏总目录 文章目录 0、printk 说明1、printk 日志等级设置2、屏蔽等级日志控制机制3、printk打印常用方式4、printk打印格式0、printk 说明 在开发Linux device Driver或者跟踪调试内核行为的时候经常要通过Log API来trace整个过程,Kernel API printk()是整个Kern…

Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL

导航&#xff1a; 【Java笔记踩坑汇总】Java基础进阶JavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线MySQL高级篇设计模式面试题汇总源码_vincewm的博客-CSDN博客 目录 一、基本区别 1.1 基本特性 1.2 Oracle和MySQL如何做技术选型&#xff1f; 1.3 RDBM…

图解java.util.concurrent并发包源码系列——深入理解ReentrantReadWriteLock读写锁,看完可以吊打面试官

图解java.util.concurrent并发包源码系列——深入理解ReentrantReadWriteLock读写锁&#xff0c;看完可以吊打面试官 ReentrantReadWriteLock的作用ReentrantReadWriteLock的原理ReentrantReadWriteLock源码解析构造方法获取写锁和读锁对象计算读锁被持有数和写锁被持有数的位移…

爆笑,打地鼠的极限,PyAutoGUI的开始~

游戏地址&#xff1a;http://www.4399.com/flash/178030_3.htm 视频教程地址&#xff1a;https://www.bilibili.com/video/BV1gm4y1x7QW/ 介绍 当提到自动化控制鼠标和键盘的Python库时&#xff0c;pyautogui是一个不可忽视的工具。它为用户提供了简单而强大的功能&#xff…

【JAVA基础】- 同步非阻塞模式NIO详解

【JAVA基础】- 同步非阻塞模式NIO详解 文章目录 【JAVA基础】- 同步非阻塞模式NIO详解一、概述二、常用概念三、NIO的实现原理四、NIO代码实现客户端实现服务端实现 五、同步非阻塞NIO总结 一、概述 NIO&#xff08;Non-Blocking IO&#xff09;是同步非阻塞方式来处理IO数据。…

主题模型分析-【LDA】

主题模型能够自动将文本语料库编码为一组具有实质性意义的类别。这些类别称为主题。 主题模型分析的典型代表就是本篇文章将要介绍的隐含迪利克雷分布&#xff0c;也就是LDA。 假设我们有一个文档或者新闻的集合&#xff0c;我们想将他们分类为主题。 我们设置好主题数量后&am…