C#下内存管理--垃圾收集

章节安排

  1. 内存管理简介
  2. 垃圾回收机制
  3. 性能问题
  4. C#下非托管资源的处理
  5. 要强调的几点
  6. References

内存管理简介

对于任何一种编程语言,内存管理都是不得不提很重要的一块内容,但可惜的是目前为止没有任何一种编程语言对内存管理处理的非常完美,每种语言都在兼顾性能 效率,语法语义易用性等方面折中中有所侧重。例如较之于C#,JAVA等语言C++号称不需要垃圾收集,因为C++本身产生的垃圾很少,诚然这是C++的 优势,这也就是为什么在内存受限或者效率优先的环境下优先考虑C++,但它的缺点也是明显的--程序员必须自己控制内存管理,很容易产生内存泄漏,这同时 也造就了C++很难掌握。感谢摩尔定律吧,它促使了垃圾收集这个概念的出现,但较之C++直接操纵内存释放,再牛逼的垃圾收集算法也无法抹去那一层性能上 的损失。

在讨论之前我们先明确一点:内存中数据按所处位置不同可以分为栈内存和堆内存,栈的主要作用是追踪函数调用之间的数据传递(栈上所存储 的数据类型通常是int,char,long,指针等内置值类型和struct。注意一点在多线程环境下,每个线程都有自己的栈。)所以栈的内存管理通常 由操作系统负责。而我们所说的内存管理,大都讨论的是堆上内存管理(分配在堆上的类型一般是自定义引用类型:类,接口,字符串,对象实例,C#中委托 等)。关于这一点详情请参照Under  the hood of NET Management。

内存内存管理从生命周期上来分可以分为三个阶段:内存分配,内存生命周期内管理,内存的释放。每一阶段都与程序的运行效率关系密切,以C++为例,在新版 的C++标准中Unique_ptr取代auto_ptr,move语义,引入右值引用等措施极大地提高了STL的效率(详细信息参考Refereces 中关于C++的链接)。而兼顾讨论内存管理的所有内容有点不现实,本篇主要关注内存的释放,确切来讲是C#的垃圾回收。

垃圾回收机制       

首先声明一点所谓垃圾回收,回收的是分配在托管堆上的内存,对于托管堆外的内存,它无能为力。

讨论垃圾回收机制就不得不提内存的分配,在C运行时堆(C-runtime heap)中,堆是不连续的,我们new一个新的对象时,系统会检查内存,找一块足够大的内存然后初始化对象,对象被销毁后,这块空间会用于初始化新的对 象。这样做有什么弊端?随着程序运行一直有对象生成释放,内存会变得碎片化,这样有新的大的对象要生成时就必须扩展堆的长度,碎片内存无法得到充分利用, 还有一个弊端是每次创建一个对象时都要检查堆内存,效率不高。而C#托管堆采取连续内存存储,新创建对象时只要考虑剩下的堆内存是否足够大就成,但一直生 成对象而不析构会使托管堆无限增大,怎么维护这样一块连续内存呢?这也就引出了垃圾回收机制。托管堆的大小是特定的,垃圾收集器GC负责当内存不够的时候 释放掉垃圾对象,copy仍在使用的对象成一块连续内存。而这就带来了性能问题,当对象很大的时候,频繁的copy移动对象会降低性能,所以C#的垃圾收 集引入了世代和大对象堆小对象堆的概念。

所谓大对象堆小对象堆从字面意义就能看出其作用,大对象堆主要负责分配大的对象,小对象堆分配小的。但对象大小怎么确定呢?在.NET Framework中规定,如果对象大于或等于 85,000 字节,将被视为大型对象。当对象分配请求传入后,如果符合该大小阈值,便会将此对象分配给大型对象堆。这个85000字节是根据性能优化的结果确定。值得 注意的是垃圾回收对大对象堆和小对象堆都起作用。那么分大对象和小对象堆作用是什么呢?还是性能,对于大对象和小对象区别对待采取不同灵活的垃圾回收策略 必定比一棍子打死死板的采用同一种策略要好。下面我们讨论一下在SOH和LOH不同的垃圾收集策略:

先说一下世代,之所以分世代,是因为在第0代就能清除大部分对象。请注意,世代是个逻辑上的概念,物理上并没有世代这个数据结构。以小对象堆垃圾回收为 例:当一个对象被创建的时候,它被定义为第0代对象,而经历一次垃圾收集后还存余的对象就被归入了第1代对象,同理经过两次或者两次以上仍然存在的对象就 可以看成第2代对象。虽然世代只是逻辑概念,但它却是有大小的,对于SOH对象来说,由于每次垃圾回收都会压缩移动对象,所以世代数越大越在堆底。经历一 次垃圾回收,对象都会被移入下一个世代的内存空间中(下图以小对象堆上垃圾回收为例。对象W至少经过两次垃圾回收而不死,所以放入世代2,X经历了一次垃 圾回收)。而每次一个世代内存达到其阙值,都会引发垃圾收集器回收一次。这么做的好处就是每次垃圾回收器只是回收一个世代的内存,从整体上来看,减少了对 象复制移动的次数。

以上讨论都是针对于SOH,对于SOH对象来说复制移动较之LOH成本性能要小一点,那么对于LOH呢?复制一个LOH的对象并且清除原来内存位置的字 节,成本相当大,怎么确保它的垃圾回收呢?首先从物理上来说,LOH在托管堆的堆底,SOH在其上,逻辑上讲LOH对象都分配在第二世代,也就是说前边第 0代和第1代的垃圾收集回收的都是第OH对象,这也就解释了为什么前两个世代垃圾回收中允许复制移动对象。但对于第二世代垃圾回收呢?第二代垃圾回收之后 的对象仍是第二世代,其回收时并不移动仍在使用的对象,压缩空间,而只是清除垃圾对象。当一个新LOH对象创建时,它会从堆底遍历寻找LOH中能够满足要 求的内存,如果没有接着向堆顶创建(这个过程和C运行时工作原理一样,所以也存在相同的弊端,LOH堆内存有可能存在碎片)。此时如果堆顶已经超出阙值, 引发垃圾回收器回收内存空间。

从上边讨论中我们可以总结一下:我们new出一对象时它要么小对象会被放入第0代,大对象会被分在LOH中,只有垃圾回收器才能够在第1代和第2代中“分配”对象,这里所说分配对象是指移动复制对象。

以上就是垃圾回收机制,有一块最重要的一点没有讨论,就是垃圾收集器GC怎么判断该对象是垃圾对象。关于这一点可以参考链接。

性能问题

由上边讨论我们可以看出,自动化垃圾回收是需要付出成本的,而世代和大对象堆/小对象堆这些概念的引入是尽可能的降低这一成本。但性能问题不可避免,性能 数据分析可以很好的帮助我们了解避免些许问题,关于性能分析工具及方法请参考References中链接。

C#下非托管资源的处理--Disposable模式

我们上边说过,垃圾回收器只能收集托管堆的内存,但对于堆外内存比如HWnds,数据库连接,GDI句柄,safeHandle。这样就有一个问题:垃圾 收集器不会确定地运行,其结果可能会使您的对象在上次引用之后很长时间不能被终结。如果你的对象占用了昂贵或稀少的资源(如一个数据库连接),这是不能被 接受的。为了避免无休止地等待垃圾收集器运行,拥有资源的类型应该实现 IDisposable 接口,然后该类型资源的使用方会及时地释放那些资源。Joe Duff在其网站中给了相关注意细节,并提供了一种Disposable模式。可以参考一下链接了解一下,使你的程序写的更加优雅健壮。(MSDN关于 IDisposable例子中也采用这种方法)

http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae

要强调的几点

1. 值得注意的是虽然微软目前不会移动压缩LOH,但是将来可能会,所以如果分配了大型对象并希望确保它们位置不被移动,则应该将其固定起来。

2. 这篇文章是基于本人理解,有可能有出入,详细信息可以参照链接,并欢迎指正。

References

C++

http://en.cppreference.com/w/

http://zh.wikipedia.org/wiki/C++0x

http://blog.csdn.net/zentropy/article/details/6973411

http://www.codeproject.com/Articles/71540/Explicating-the-new-C-standard-C-0x-and-its-implem#RValues http://www.codeproject.com/Articles/101886/Standard-C-Library-Changes-in-Visual-C-2010

C#

http://msdn.microsoft.com/zh-cn/magazine/bb985011(en-us).aspx

http://msdn.microsoft.com/zh-cn/magazine/cc534993.aspx

http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae

性能问题和多语言交互

http://msdn.microsoft.com/zh-cn/magazine/ee309515.aspx

http://msdn.microsoft.com/zh-cn/magazine/cc163528.aspx

http://msdn.microsoft.com/zh-cn/magazine/cc163316.aspx

http://msdn.microsoft.com/zh-cn/magazine/cc163392.aspx

垃圾收集发展史:

http://blog.csdn.net/KAI3000/article/details/314628

http://blog.csdn.net/hellothere/article/details/2115422

http://blog.csdn.net/hellothere/article/details/2245734

时间: 2024-12-23 17:28:51

C#下内存管理--垃圾收集的相关文章

八.OC基础加强--1.autorelease的用法 2.ARC下内存管理 3.分类(category)4.block的学习

1.autorelease的用法   1.自动释放池及autorelease介绍 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的. (2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中 . 2.为什么会有autorelease? OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放. 但有些情况下,开发者并不能确定某些对象何时释放.这时候就需要自动释放池. 它的好处是: (1)不需要再关心对象释放的时间 : (2)不需要再关

Java虚拟机的内存管理----垃圾收集器

1.Serial收集器 优点,是简单而高效,单线程避免了线程交互的开销. 缺点,进行垃圾回收时需要Stop the world(暂停所有用户线程). 2.ParNew收集器 它是Serial收集器的多线程版本,新生代才有多线程并行收集.是CMS收集器(下文会介绍)的默认新生代收集器. ParNew在单CPU的情况下,会比Serial收集器效率更差,因为多线程交互的开销. 但是,如今的计算机普遍是多CPU多核,而ParNew默认的线程数量是CPU的数量.因此它更加适应潮流. 3.Parallel

深入理解_JVM内存管理垃圾收集器05

1.垃圾收集器(内存回收方法的具体实现): 名词解释: 并行(Parallel):多条垃圾线程并行工作,但是此时用户线程仍然处于等待状态. 并发(Concurrent):指用户线程与垃圾收集线程同时执行(并不一定是并行的,可能会交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上. HotSpot虚拟机包含的所有收集器如下图: 说明: (a)JDK1.6_Update14之后引入了Early Access版G1收集器. (b)如果两个收集器之间存在连线,就说明它们可以搭配使用. <Y

深入理解_JVM内存管理垃圾收集算法04

1.垃圾收集算法(方法论): 定义:JVM通过GC来回收堆和方法区中的内存. GC的基本原理:首先会找程序中不再被使用的对象:然后回收这些对象所占用的内存. 算法分类: (1) 按照基本回收策略分: <1>引用计数(Reference Counting): 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收集计数为0的对象.此算法最致命的是无法处理循环引用的问题. <2> 标记-清除(Mark-Sweep): Before G

glibc下的内存管理

在解码过程中我们也遇到了类似的问题,第一次解码的音频比较大60s,耗了3G的内存,reset之后内存并没有退还给操作系统,第二次即使解一个10s的音频 几周前我曾提到,我被项目组分配去做了一些探究linux下内存管理机制的活儿.因为我们的产品遇到了一些与之相关的“诡异”问题.这些问题以及相关情况可以概括如下: 先介绍一下相关的背景.由于我们是3D软件,所以用户经常会有“导入/导出”各种geometry的需求.而一个存储这些数据的文件,可能含有不止一个geometry,而且每个geometry中也

OC内存管理详解

前言 由于移动设备的内存有限,所以我们需要对内存进行严格的管理,以避免内存泄露造成资源浪费.在OC中,只有对象才属于内存管理范围,例如int.struce等基本数据类型不存在内存管理的概念.在iOS开发中,对内存的管理实际上就是对引用计数器的管理. OC内存管理的三种方式 自动垃圾收集(Automatic Garbage Collection): 手动引用计数器(Manual Reference Counting)和自动释放池: 自动引用计数器(Automatic Reference Count

内存管理机制

Objective-C中提供了两种内存管理机制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求. ARC: ARC是Auto Reference Counting的缩写,即自动引用计数,由编译器在代码合适的位置中自动添加retain/Release/Autorelease/dealloc方法从而进行内存管理. ARC几个要点: 在对象被创建时 retain count

iOS经典面试题总结--内存管理

iOS经典面试题总结--内存管理 内存管理 1.什么是ARC? ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被创建时retain count+1,在对象被release时count-1,当count=0时,销毁对象.程序中加入autoreleasepool对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁.那么ARC是为了解决MRC手动管理内存存在的一些而诞生的. MRC下内存管

iOS之内存管理浅谈

1.何为ARC ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被创建时retain count+1,在对象被release时count-1,当count=0时,销毁对象.程序中加入autoreleasepool对象会由系统自动加 上autorelease方法,如果该对象引用计数为0,则销毁.那么ARC是为了解决MRC手动管理内存存在的一些而诞生的. MRC下内存管理的缺点: 释放一个堆内存时,首先要确定指向这