`
touchmm
  • 浏览: 1055622 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

C# 值类型和引用类型的内存管理

阅读更多

本次日志,我们来重点聊一聊软件开发过程中,如何提高性能方面的问题。这是软件开发或研发过程中深层次的问题,这篇文章主要从内存分配和内存回收两方面说明,我们软件代码编写过程中,计算如何来工作的。在此你可以了解内存管理的过程和方式,以便在以后的软件开发中注意它、利用它。

值类型包括:int,float,double,bool,结构,引用,表示对象实例的变量

引用类型包括:类和数组;比较特殊的引用类型string、object

一般情况下:值类型存储在堆栈中(不包括包含在引用中的值类型,如类的值字段,类中的引用字段,数组的元素这些都是随引用存储在受管制的堆中);引用类型存储在受管制的堆中,为什么说是受管制的堆,下面会具体来谈。

几个概念:

虚拟内存:32位的计算机,每个进程拥有4G的虚拟内存。

受管制的堆(托管堆):受谁的管制?当然是无用单元收集器,即垃圾收集器。如何管理下面再说?

无用单元收集器:垃圾收集器除了会压缩托管堆、更新引用地址、还会维护托管堆的信息列表等等。

关于值类型的存储先看如下代码:

{

int age=20;

double salary=2000;

}

上面定义的两个变量,int age,告诉编译器需要给我分配4个字节的内存空间来存储age值,它是存储在堆栈上的。堆栈属入先进后出的数据结构,堆栈是从高位地址到低位地址存储数据的。计算机寄存器中保持着一个堆栈指针,他总是指向堆栈最底端的自由空间地址,当我们定义一个int类型的值时,堆栈指针递减四个字节的地址;当变量出了作用域后,堆栈指针相应的相应的递增四个字节的地址,它只是堆栈指针的上下移动,所以堆栈的性能是相当高的。

下面看一下引用类型的存储,还是先看代码:

{

Customer customerA;

customerA=new Customer(); //假定Customer实例占据32个字节

}

上面的代码第一行先声明了一个Customer引用,引用的名字为customerA,在堆栈上给此引用分配存储空间,存储空间的大小为4个字节,因为它只存储了一个引用,这个引用所指向的才是即将存储Customer实例的空间地址,注意此时customerA并没有指向具体的空间,它只是分配了一个空间而已。

第二行执行过程中,.net环境会搜索托管堆,寻找第一个未使用的、连续的32个字节空间分配给类的实例,并设置customerA指向这段空间的顶端位置(堆的空间是从低到高使用的)。当引用变量出作用域后,堆栈中的引用会无效,当托管堆中的实例还在,直到垃圾收集器对其进行清理。

到这里细心的读者可能会有些疑问,是不是定义引用类型时,计算机要搜索整个堆,寻找足够大的内存空间来存储对象呢?这样会不会效率很低?如果没有足够大的连续的空间呢?这个就要谈到“托管”了。堆是受垃圾收集器管理的,.net在执行垃圾收集器时释放能释放的所有对象,并压缩其他对象,然后把所有自由空间组合在一起移动到堆的顶端, 形成连续的块,同时更新其他移动对象的引用。如果再有对象定义,可以很快找到合适的空间。如此看来托管堆工作方式与堆栈类似,它是通过堆指针来完成空间的分配和回收的。

上面谈了.net对内存空间分配的管理过程和方式,接下来谈一谈对内存的回收过程。谈到资源的清理,不得不提到的两个概念和三个方法。

两个概念为:托管资源和非托管资源。

托管资源接受.net framework的CLR(通用语言运行时)的管理;非托管资源则不受它的管理。

三个方法为:Finalize(),Dispose(),Close()。

一、Finalize()为析构方法,清除非托管资源。

在类中定义方式:

public ClassName{

~ClassName()

{

//清理非托管资源(如关闭文件和数据库联接等)

}

}

它的特点是:

1. 运行不确定性。

它是受垃圾收集器的管理,当垃圾收集器工作时,会调用此方法。

2. 性能开销大。

垃圾收集器工作方式为,对象如果执行了Finalize()方法,垃圾收集器第一次执行时,会把它放在一个特殊的队列中;第二次执行的时候才会删除此对象。

3. 不能显示定义和调用,定义为析构方法形式。

基于以上特点,最好不要执行Finalize()方法,除非类确实需要它或与其他两个方法结合来用。

二、Dispose()方法,可清除一切需要清除的资源,包括托管和非托管资源。

定义如下:

public void Dispose()

{

//清理应该清理的资源(包括托管和非托管资源)

System.GC.SuppressFinalize(this); //这一句很重要,下面会解释原因。

}

它的特点:

1. 任何客户代码都应显示调用这个方法,来释放资源。

2. 由于第一点的原因,一般要做一个备份,这个备份一般由析构方法来担任角色。

3. 定义此方法的类,必须继承IDisposable接口。

4. 语法关键字using等同于调用Dispose(),使用using时,它是默认调用Dispose()方法。所以使用using的类也要继承IDisposable接口。

Dispose()方法比较灵活,在资源不需要时立即释放。它是资源的最终处理,调用它意味着会最终删除对象。

三、Close()方法,暂时处置资源的状态,可能以后还会使用。一般处理非托管资源。

定义如下:

public viod Close()

{

//对非托管资源状态的设置,如关闭文件或数据库连接

}

它的特点:

对非托管资源状态的设置,一般是关闭文件或数据库连接。

下面写一个综合且又经典的的例子,利用代码演示一下各部分的作用:(为了省事,这个例子是从网上杜撰来的,只要说明问题就可以了,你说呢。在此应该感谢代码原创者,感谢他写了这么经典和易懂的代码,我们受益匪浅!)

public class ResourceHolder : System.IDisposable

{

public void Dispose()

{

Dispose(true);

System.GC.SuppressFinalize(this);

// 上面一行代码作用是防止"垃圾回收器"调用这个类中的析构方法

// " ~ResourceHolder() "

// 为什么要防止呢? 因为如果用户记得调用Dispose()方法,那么

// "垃圾回收器"就没有必要"多此一举"地再去释放一遍"非托管资源"了

// 如果用户不记得调用呢,就让"垃圾回收器"帮我们去"多此一举"吧 ^_^

// 你看不懂我上面说的不要紧,下面我还有更详细的解释呢!

}

protected virtual void Dispose(bool disposing)

{

if (disposing)

{

// 这里是清理"托管资源"的用户代码段。

}

// 这里是清理"非托管资源"的用户代码段。此处为析构方法的实际执行代码,为了避免客户代码忘记显示调用Dispose()方法,所作的备份。

}

~ResourceHolder()

{

Dispose(false); // 这里是清理"非托管资源"

}

}

如果看不明白以上代码,一定要仔细阅读以下解释,很经典,不看会后悔呦。

这里,我们必须要清楚,需要用户调用的是方法Dispose()而不是方法Dispose(bool),然而,这里真正执行释放工作的方法却并不是Dispose(),而是Dispose(bool) ! 为什么呢?仔细看代码,在Dispose()中,调用了Dispose(true),而参数为"true"时,作用是清理所有的托管资源和非托管资源;大家一定还记得我前面才说过,"使用析构方法是用来释放非托管资源的",那么这里既然Dispose()可以完成释放非托管资源的工作,还要析构方法干什么呢? 其实,析构方法的作用仅仅是一个"备份"!

为什么呢?

格地说,凡执行了接口"IDisposable"的类,那么只要程序员在代码中使用了这个类的对象实例,那么早晚得调用这个类的Dispose()方法,同时,如果类中含有对非托管资源的使用,那么也必须释放非托管资源! 可惜,如果释放非托管资源的代码放在析构方法中(上面的例子对应的是 " ~ResourceHolder() "),那么程序员想调用这段释放代码是不可能做到的(因为析构方法不能被用户调用,只能被系统,确切说是"垃圾回收器"调用),所以大家应该知道为什么上面例子中"清理非托管资源的用户代码段"是在Dispose(bool)中,而不是~ResourceHolder()中! 不过不幸的是,并不是所有的程序员都时刻小心地记得调用Dispose()方法,万一程序员忘记调用此方法,托管资源当然没问题,早晚会有"垃圾回收器"来回收(只不过会推迟一会儿),那么非托管资源呢?它可不受CLR的控制啊!难道它所占用的非托管资源就永远不能释放了吗? 当然不是!我们还有"析构方法"呢! 如果忘记调用Dispose(),那么"垃圾回收器"也会调用"析构方法"来释放非托管资源的!(多说一句废话,如果程序员记得调用Dispose()的话,那么代码"System.GC.SuppressFinalize(this);"则可以防止"垃圾回收器"调用析构方法,这样就不必多释放一次"非托管资源"了) 所以我们就不怕程序员忘记调用Dispose()方法了。

分享到:
评论

相关推荐

    C#中引用类型和值类型的区别

    而引用类型存储在堆上,虽然提供了更灵活的内存管理和生命周期控制,但堆操作相对缓慢。 - **内存消耗**:值类型在每次传递或复制时都会创建一个新的实例,可能会导致更多的内存消耗。而引用类型只传递引用,可以...

    C#基础知识 值类型、引用类型

    这两种类型在内存管理和数据处理上有着显著的区别,理解它们对于编写高效且无错的代码至关重要。 值类型(Value Types)包括基本类型如整型(int)、浮点型(float)、布尔型(bool)以及枚举(enum)等,还有结构...

    C#值类型与引用类型区别

    在C#编程语言中,值类型和引用类型是两种基本的数据类型,它们在内存管理和操作方式上有着显著的差异。了解这些差异对于编写高效、安全的代码至关重要,尤其是在处理大量数据或复杂对象时。 首先,让我们来探讨值...

    C#引用类型和值类型的区别

    在C#编程语言中,值类型和引用类型是两种主要的数据类型,它们在内存管理和行为上有显著的区别。值类型直接存储其实际值,比如整数、浮点数、布尔值,以及自定义的结构体(struct)和枚举类型。变量在栈中分配内存,...

    理解 C#值类型与引用类型 (2).docx

    在实践中,根据数据的特性、内存管理需求以及性能要求,灵活运用值类型和引用类型,是每个C#开发者必备的技能。 7. 参考 学习更多关于C#类型系统的知识,可以参考《CLR via C#》等专业书籍,以及官方文档和权威在线...

    理解 C#值类型与引用类型.docx

    在C#编程中,理解值类型和引用类型是至关重要的,因为它们决定了变量如何存储、复制和传递。本文将深入探讨这两个概念,并提供实用的建议,以帮助开发人员避免潜在的错误和性能问题。 1. **通用类型系统** C#的...

    C#中的值类型与引用类型深度解析:理解内存中的数据处理

    在C#编程语言中,数据类型可以分为两大类:值类型(Value Types)和引用类型(Reference Types)。这种分类对于理解变量如何在内存中存储以及如何管理这些变量的生命周期至关重要。本文将深入探讨这两种类型的区别,...

    C#内存管理机制C#内存管理机制

    在C#中,值类型和引用类型之间可以进行转换,称为装箱和拆箱。装箱可以将值类型转化为引用类型,而拆箱可以将引用类型转化为值类型。 垃圾收集器是C#中的一个机制,它负责清理内存中的垃圾对象。垃圾收集器可以在...

    C#中的值类型和引用类型

    在C#编程语言中,值类型和引用类型是两种基本的数据类型,它们在内存管理和数据存储方式上有着显著的区别,对程序性能和行为有直接影响。理解这两种类型是编写高效、安全C#代码的关键。 值类型(Value Types): 1....

    训练师脚本:《使用C#中的枚举、结构、值类型和引用类型

    ### 使用C#中的枚举、结构、值类型和引用类型 #### 枚举(Enum) **定义**: 枚举是一种特殊的类,它定义了一组固定的命名常量,通常用于表示一系列预定义的值。 **语法**: ```csharp [访问修饰符] enum 枚举名 { ...

    C#源代码-值类型和引用类型.zip

    选择使用哪种类型取决于具体的需求,如性能、内存管理和代码复杂性等因素。在设计和实现C#程序时,合理地使用值类型和引用类型可以提高程序的性能和可维护性。在处理简单数据时,使用值类型;在需要面向对象特性和...

    c#值类型和引用类型使用示例

    值类型和引用类型在性能、内存管理和垃圾回收方面有着显著的不同。值类型在内存分配上更为高效,但可能导致更多拷贝,而引用类型则可能涉及更复杂的内存管理,尤其是涉及大量对象创建时。在选择使用哪种类型时,需要...

    C#基础知识 值类型装箱

    本文将深入探讨C#中的“值类型装箱”这一概念,这是理解C#内存管理和对象模型的关键知识点。 首先,我们要知道在C#中,数据类型分为值类型(Value Types)和引用类型(Reference Types)。值类型包括基本类型如int...

    浅析C# 内存管理

    ### 浅析C# 内存管理 #### 内存管理概述 ...通过掌握值类型与引用类型的差异、垃圾回收的工作原理以及如何正确管理非托管资源,开发者可以更有效地利用.NET提供的自动化内存管理特性,同时确保程序的稳定性和效率。

    c# 引用类型和值类型区别 (2).docx

    而引用类型需要通过指针间接访问,可能涉及额外的内存管理和垃圾回收,因此在大量创建和销毁对象时可能导致性能下降。 5. **默认值**:值类型具有默认构造函数,会赋予默认值,如0、'\0'等。而引用类型如果不初始化...

    C#笔记值传递和引用传递

    在C#编程语言中,了解值传递和引用传递的概念至关重要,因为这直接影响到函数...总的来说,理解和掌握值传递、引用传递以及C#中的构造函数、内存管理和控制流语句是C#编程的基础,这对于编写高效、可靠的代码至关重要。

    C#一个类型(值或引用类型)的实例在内存中的字节.rar

    在C#编程语言中,类型可以分为值类型和引用类型,这两种类型的实例在内存中存储的方式有所不同,这对于理解和优化代码性能至关重要。值类型包括原始数据类型(如int、char、bool)以及结构(struct),而引用类型则...

    c#基础系列之值类型和引用类型的深入理解

    例如,在创建和传递大型对象或需要对象在不同方法间共享时,引用类型允许更灵活的内存管理和对象引用共享。此外,在某些特定的场景下,引用类型的性能可以超过值类型,特别是在涉及大量间接访问和复杂对象状态管理的...

    第四章 C#预定义值类型和引用类型.docx

    这两种类型在内存管理和操作方式上存在显著差异。 1. **值类型**:值类型包括整型、浮点型、布尔型、字符型以及结构(如DateTime、Decimal)。它们在内存中直接存储实际的值,通常在栈上分配。例如,当你声明一个`...

    值类型和引用类型Demo

    在编程领域,值类型和引用类型是两种基本的数据类型,它们在内存管理和数据处理上有着显著的区别。了解这两种类型的概念和特性对于编写高效、安全的代码至关重要。 值类型(Value Type)主要包括整型(如int)、...

Global site tag (gtag.js) - Google Analytics