章节安排

  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

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

  1. RHEL 7 下内存管理小记

    RHEL 7 下内存管理小记 一.Overview 众所周知,在 Linux 操作系统中有三个目录非常有趣好玩. /dev /run /proc 一个个解释下,/dev 用于对特殊设备(BTW:特殊设 ...

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

    1.autorelease的用法   1.自动释放池及autorelease介绍 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的. (2)当一个对象调用auto ...

  3. .NET 自动内存管理(垃圾收集GC)

    自动内存管理(垃圾收集GC) 在面向对象的环境里, 要使用资源,必须为响应 的类型分配一定 的内存空间.下面是访问一个资源所需要的几个步骤: 1. 调用中间语言(IL)的newobj 指令.当我们用N ...

  4. JVM内存管理---垃圾收集器

    说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态 ...

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

    1.Serial收集器 优点,是简单而高效,单线程避免了线程交互的开销. 缺点,进行垃圾回收时需要Stop the world(暂停所有用户线程). 2.ParNew收集器 它是Serial收集器的多 ...

  6. 一篇关于arc下内存管理的老文章,包含各种冷门修饰符(关于内存),写的较好,mark

    http://blog.csdn.net/zhibudefeng/article/details/7746201

  7. glibc下的内存管理

    在解码过程中我们也遇到了类似的问题,第一次解码的音频比较大60s,耗了3G的内存,reset之后内存并没有退还给操作系统,第二次即使解一个10s的音频 几周前我曾提到,我被项目组分配去做了一些探究li ...

  8. 06OC之内存管理

    在高级语言中,例如C#是通过垃圾回收机制(GC)来解决这个问题,但是在OC并没有类似的垃圾回收机制,因此必须由程序员手动去维护.今天就讲讲OC中的内存管理: 一.内存管理原理 在Xcode4.2之后的 ...

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

    iOS经典面试题总结--内存管理 内存管理 1.什么是ARC? ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被 ...

随机推荐

  1. 一位ACM过来人的心得(转)

    励志下! 刻苦的训练我打算最后稍微提一下.主要说后者:什么是有效地训练? 我想说下我的理解.很多ACMer入门的时候,都被告知:要多做题,做个500多道就变牛了.其实,这既不是充分条件.也不会是必要条 ...

  2. 数学语言和程序语言的对比:面向过程与面向集合&命题

    共同之处:都使用字符串或数值来引用一个客观实体.当然数字和字符串也可以作为实体对象,这取决于人的解释. 不同之处:数学语句每一行都给出了一个结论, 程序语句的每一行都定义了一个过程.注意这里所指的程序 ...

  3. HDU 1041 Computer Transformation

    这道题目的意思是:一开始有一个数字 1 ,在接下来的时间中,计算机会按照如下规则进行扩展:                0 –> 1 0                1 –> 0 1 ...

  4. MVC中前台所得

    前台页面时间格式修改: @item.CreateTime.ToString("yyyy-MM-dd hh:mm:ss") 前台方法调用传参数: <a href="# ...

  5. Delphi Unable to invoke Code Completion due to errors in source code

    这时因为在.pas文件中存在delphi无法识别的编码,也就是说.pas文件中的字符并非是纯粹的可由文本文件编辑器所能识别的编码.所以,delphi就不可能有效地解释这些编码.因而就出现了自动代码提示 ...

  6. ZOJ 3879 Capture the Flag

    以此题纪念我写的第一篇acm博客,第一道模拟:) http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3879 题意非常复杂,感觉 ...

  7. “自私”的Linux

    导读 “如果当时我真的知道从头建立一个操作系统的难度,肯定是不会有勇气去做的.”1991年8月25日,随着林纳斯·托瓦兹(Linus Torvalds)这句“天真”的描述,Linux系统正式与世人见面 ...

  8. [Everyday Mathematics]20150122

    设 $f:[0,1]\to [0,1]$. (1). 若 $f$ 连续, 试证: $\exists\ \xi\in [0,1],\st f(\xi)=\xi$. (2). 若 $f$ 单调递增, 试证 ...

  9. UI篇---RadioButton(单选按钮)

    单选按钮RadioButton在Android平台上也应用的非常多,比如一些选择项的时候,会用到单选按钮,实现单选按钮由两部分组成,也就是RadioButton和RadioGroup配合使用 Radi ...

  10. Drawer Layout

    http://developer.android.com/training/implementing-navigation/nav-drawer.html#DrawerLayout <menu ...