发文章
发文工具
撰写
网文摘手
文档
视频
思维导图
随笔
相册
原创同步助手
其他工具
图片转文字
文件清理
AI助手
留言交流
本次日志,我们来重点聊一聊软件开发过程中,如何提高性能方面的问题。这是软件开发或研发过程中深层次的问题,这篇文章主要从内存分配和内存回收两方面说明,我们软件代码编写过程中,计算如何来工作的。在此你可以了解内存管理的过程和方式,以便在以后的软件开发中注意它、利用它。
值类型包括: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
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()方法了。
来自: 昵称10504424 > 《C#》
0条评论
发表
请遵守用户 评论公约
.NET Framework 自动内存管理机制深入剖析 (C#分析篇) - syinter...
NET Framework 自动内存管理机制深入剖析 (C#分析篇) - syinter...* * public void Dispose() * * 该成员支持 .NET 框架结构,因此不适用于直接从代码中使用。...
.NET基础拾遗(1)类型语法基础和内存管理基础
①当每个包含Finalize方法的类型的实例对象被分配时,.NET会在一张特定的表结构中添加一个引用并且指向这个实例对象,暂且称该表为“带...
托管资源和非托管资源
托管资源和非托管资源。Dispose()的执行代码显式释放由对象直接使用的所有未托管资源,并在所有实现IDisposable接口的封装对象上调用Dispose()。传递给Dispose(bool)的参数表示Dispose(bool)是由析构函...
常用的.NET面试问题 - 1
答:托管代码是由CLR而不是操作系统执行的代码,编译器首先将托管代码编译为中间语言代码。另一方面,非托管代码是在CLR环境或操作系统...
C# vs C++之二:GC vs RAII
GC在回收无用对象资源时,可以自动回收托管资源(比如托管内存),但对于非托管资源(比如Socket、文件、数据库连接)必须在程序中显式释放。Finalizer是对象被GC回收之前调用的终结器,初衷是在这里释...
托管代码和非托管代码有什么区别?
托管代码和非托管代码有什么区别? 托管和非托管是修饰内存的。实际上只是堆上的内存管理,栈内存和以前的一样,函数退出则释放,heap上的内存,非托管内存需要自己分配,调用构造函数(c需要,c...
c#托管资源和非托管资源区别
c#托管资源和非托管资源区别在了解Finalize和Dispose之前,我们需要了解两个概念,一个是托管资源,一个非委托资源。其中托管资源一般是指被CLR控制的内存资源,这些资源的管理可以由CLR来控制,例如程...
.net垃圾回收机制详解
.net垃圾回收机制详解1. 自动内存管理和GC 在原始程序中堆的内存分配是这样的:找到第一个有足够空间的内存地址(没被占用的),然后将该内存分配。2. GC工作方式 首先我们要知道托管代码中的对...
GC回收机制
GC回收机制GC回收机制。我说了GC是随机的,哪么你只管点你的,不一会GC就会来回收的(这里我们可以认为,内存中存在一定数量的垃圾之后,GC会来),要证明GC来过我们把AA类改成 public class AA.dispos...
微信扫码,在手机上查看选中内容