.NET的内存分配和释放解析

2017-03-27 14:20王红伟王红纪
科技资讯 2017年2期

王红伟+王红纪

摘 要:在面向对象的程序环境中,任何一个程序都需要使用到计算机资源,程序中的每一个类型都代表着程序需要的资源。程序在使用这些资源过程中,要经历一个分配内存给类型资源、初始化内存及数据类型、访问数据成员信息、销毁并清理资源、释放内存的过程。

关键词:.NET 内存分配 垃圾回收器 内存释放

中图分类号:TP311 文献标识码:A 文章编号:1672-3791(2017)01(b)-0028-02

在.NET中,内存中的资源(即所有二进制信息的集合)分为“托管资源”和“非托管资源”。托管资源必须接受.NET的CLR(通用语言运行时,.NET框架的底层)的管理(诸如内存类型安全性检查),而非托管资源则不必接受.NET的通用语言运行时的管理。.NET对内存的管理首先是管理托管资源和非托管资源所占用的内存分配和释放;其次是寻找不再使用的对象,释放其占用的内存,以及释放非托管资源所占用的内存;最后是释放内存之后,出现了内存碎片,垃圾回收器移动一些对象,以得到整块的内存,同时所有的对象引用都将被调整为指向对象新的存储位置。

1 内存分配

.NET平台引入高效的、安全的托管执行环境——通用语言运行时。通用语言运行时管理内存的区域,主要有三块,依次是:(1)线程的堆栈,用于分配值类型实例对象,它主要由操作系统管理,当值类型实例所在方法结束时,其存储单位自动释放,堆栈的执行效率高,但存储容量有限。(2)GC堆(中文名称垃圾回收,是.NET中对内存管理的一种功能),用于分配小对象实例。(3)LOH(Large Object Heap)堆,用于分配大对象实例。

.NET CLR将所有资源分配到托管堆上,当一个线程初始化,运行时将预定一块未使用连续的地址空间。这块地址空间就是托管堆。堆中同时维护着一个指针,我们叫它下一个对象指针。这个指针告诉我们下一个程序对象将被分配到堆中的什么位置。在程序初期,这个指针被设置到最基本的内存地址。使用new运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,并且空间中够用,下一个对象指针将指向堆中的此对象,对象构造函数被调用,最后返回对象内存地址。

2 内存释放与回收

当一个程序使用new操作符创建一个新对象时,可能没有足够的地址空间来放置它。为了检测地址空间是否足够,托管堆会尝试把对象放到下一个对象指针位置,如果下一个对象指针移动到超过地址空间边界,那说明堆已满,GC则进行垃圾回收。垃圾回收器跟踪并回收托管内存中分配的对象,定期执行垃圾回收以回收分配给没有有效引用的对象的内存。当使用可用内存不能满足内存请求时,垃圾回收会自动进行。在进行垃圾回收时,垃圾回收器首先搜索内存中的托管对象,然后从托管代码中搜索被引用的对象并标记为有效,接着释放没有被标记为有效的对象并收回内存,最后整理内存将有效对象挪动到一起。

3 内存释放与回收的模式

我们创建的类不包含非托管资源时,只需要直接使用,CLR自然会判断其生命周期结束而后回收相应的托管资源。但如果我们创建了含有非托管资源的类,CLR提供其他机制来帮助自动释放非托管资源。在.NET中提供三种模式来回收内存资源:dispose模式、finalize方法、close方法。

(1)dispose提供一种显式释放内存资源的方法。此方法用于更快更具操作性进行释放,可以使用此方法。结构和类型都可以实现IDispose,因为是对象本身释放非托管资源,所以可以用对象名来显式的调用来实现内存释放。所有实现IDisposable接口的类对象都必须调用这一方法。.NET基类库中许多类型都实现IDisposable接口,有时给这一方法提供另外的别名,例如:输入输出类中的Close方法。

(2)finalize方法是.NET的内部的一个释放内存资源的方法,由垃圾回收器自己调用。此方法被不断重写,原因是一些类通过平台调用服务或复杂的COM互操作性任务使用了非托管资源。对象类中也有这一方法,但创建的类不能重写此方法,可以通过析构函数来达到同样的效果。这一方法的作用是保证.NET对象能在垃圾回收时清除非托管资源。通用语言运行时在托管堆上分配对象时,运行库自动确定该对象是否提供一个自定义的Finalize方法。如果是这样,对象会被标记为可终结的,同时一个指向这个对象的指针被保存在名为终结队列的内部队列中。终结队列是一个由垃圾回收器维护的表,它指向每一个在从堆上删除之前必须被终结的对象。Finalize最大作用是确保非托管资源一定被释放。

(3)close和dispose其实一样。一些类中没有定义dispose的方法,只定义了close方法,而close实质上也是调用了一个私有的dispose方法,而finalize其实也是调用一个不对外公开的dispose方法。

4 内存释放与回收过程

垃圾回收时机:托管堆满,内存分配即将不足。程序员可以手动调用GC.Collect()。垃圾确认:通过根来寻找可达的对象,并做标记,然后回收没有标记的对象。垃圾回收:内存回收,实现了Finalize方法的对象用此方法实现内存回收。内存转移、合并:垃圾回收后使得内存不连续、零碎,.NET会将利用的内存合并为连续的块,然后更新对象的指针。

5 内存释放与回收时的注意事项

值类型(包括引用和对象实例)和引用类型的对象,当它们出了作用域后会自动释放所占内存。因为它们都保存在“堆栈”中,这是一种先进后出数据结构。引用类型的引用所指向的对象实例保存在“堆”中,而堆因为是一个自由存储空间,所以它并没有像“堆栈”那樣有生存期(“堆栈”的元素弹出后就代表生存期结束,也就代表释放了内存),“垃圾回收器”只对这块区域起作用。 “垃圾回收器”并不会立即执行(当堆中的资源需要释放时),因为“垃圾回收器”的调用是比较消耗系统资源的,因此,不能经常被调用。这时,用户可以调用方法System.GC.Collect()来强制执行“垃圾回收器”。可实现Dispose()方法来显式释放由对象使用的所有未托管资源。垃圾收集器在释放了它能释放的所有对象后,就会压缩其他对象,把他们都移动回堆的端部,再次形成一个连续的块。

参考文献

[1] Neo_Wu[EB/OL].http://blog.csdn.net/neo_ustc/article/details/12883185,2013.

[2] .Net垃圾回收中涉及的名称[EB/OL].http://www.cnblogs.com/hfclytze/p/3706326.html,2014.

[3] binbingg[EB/OL].http://www.cnblogs.com/chinafine/articles/864776.html,2016.

[4] 贺俊峰..Net垃圾回收器管理应用程序的内存分配和释放[EB/OL].http://hejunfeng.blog.51cto.com/3182120/627153.

[5] http://jingyan.baidu.com/article/642c9d34d7ec8f644a46f7fb.html,2013.

[6] Depaul[EB/OL].http://blog.csdn.net/leewhoee/article/details/17291953,2014.