.NET 垃圾回收与内存泄漏
> 前言
相信大家一定听过,看过甚至遇到过内存泄漏。在 .NET 平台也一定知道有垃圾回收器,它可以让开发人员不必担心内存的释放问题,因为它会自定管理内存。但是在 .NET 平台下进行编程,绝对不会发生内存泄漏的问题吗?答案是否定的,就算有了自动内存管理的垃圾回收器,也会发生内存泄漏。本文就讨论下 .NET 平台的垃圾回收器是如何工作的,进而当我们在编写 .NET 程序时避免发生内存泄漏的问题。
> 垃圾回收的基本概念
“垃圾”指的是事先分配过但后来不再被使用的内存。
垃圾回收背后的一个基本观念是:“无限访问的内存”,但是从来没有无限的内存,当机器需要分配内存但不够的时候,就需要把之前不再使用的内存——“垃圾”回收再利用。
.NET 的垃圾回收器正是这样做的:
.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每当您创建新对象时,公共语言运行时都会从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行时就会继续为新对象分配空间。 但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。(引用 MSDN 垃圾回收)
> 垃圾回收器的工作场景
每当我们创建一个对象的时候,系统会为新对象分配一块内存,如果有足够的可用内存则会直接分配;但是当内存不足的时候,此时垃圾回收器会进行一次回收操作,把不再使用的对象释放,转化为可用的内存供新对象使用。
看似很简单的工作步骤,但是垃圾回收器怎么知道确保不再使用的对象的呢?
> 垃圾回收算法
当进行一次垃圾回收操作时,会分三个步骤进行:
1. 先假设所有对象都是垃圾;
2. 标记出正在使用的对象;
标记依据:
a. 被变量引用的对象,仍然在作用域中。
比如某个类中的某个方法,方法执行了一半,如果此时发生垃圾回收,那么方法块中的变量都在作用域中,那么它们都会被标记为正在使用。
b. 被另一个对象引用的对象,仍在使用中。
3. 压缩:释放第二步中未标记的对象(不再使用,即“垃圾”)并将使用中的对象转移到连续的内存块中。
只要垃圾回收器释放了能释放的对象,它就会压缩剩余的对象,把它们都移回堆的端部,再一次形成一个连续的块。
备注:
垃圾回收器为了提升性能,使用了代机制,新建的对象是新一代,较早创建的对象是老一代,最近创建的对象是第0代。为了描述垃圾回收器的基本原理,本文不深入讨论代机制。
总之,有了垃圾回收器,我们不必自己实现代码来管理应用程序所用的对象的生存期。
既然有了自动内存管理功能的垃圾回收器,为什么还会发生内存泄漏呢?
> 托管与非托管
由公共语言运行库环境(而不是直接由操作系统)执行的代码称作托管代码,运行在 .NET 框架下,受 .NET 框架管理的应用或组件称作托管资源。.NET 中超过80%的资源都是托管资源,如 int, string, float, DateTime。
非托管资源是 .NET 框架之外的,最常见的一类非托管资源就是包装操作系统资源的对象,例如文件,窗口或网络连接,对于这类资源虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。所以,对于非托管资源,在应用程序中使用完之后,必须显示的释放它们。
所以,大部分内存泄漏都是非托管资源内存泄漏:没有显示的释放它们。
> 非托管资源内存泄漏
一个会导致内存泄漏的类:
public class Foo
{
Timer _timer; public Foo()
{
_timer = new Timer();
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
} void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Tick");
}
}
调用 Foo 类:
static void Main(string[] args)
{
Foo foo = new Foo();
foo = null; Thread.Sleep(int.MaxValue);
}
foo 虽然设置为 null,但是 foo 中的字段 _timer 依然存活,Elapsed 事件继续执行:

此类中,_timer 对象就是非托管对象,由于 _timer 的 Elapsed 事件,.NET Framework 会保持 _timer 永远存活,进而 _timer 对象会保持 Foo 实例永远存活,直到程序关闭。
为了解决这个问题,我们要显示的释放 _timer 对象:Foo 类继承 IDisposable 接口,修改后的类:
public class Foo : IDisposable
{
Timer _timer; public Foo()
{
_timer = new Timer();
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
} public void Dispose()
{
Console.WriteLine("Dispose");
_timer.Dispose();
} void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Tick");
}
}
再次调用 Foo 类,并显示调用 Dispose 方法:
static void Main(string[] args)
{
Foo foo = new Foo();
foo.Dispose();
foo = null; Thread.Sleep(int.MaxValue);
}
foo 设置为 null,_timer 对象也同时被回收,Elapsed 事件停止:

> 非托管资源的垃圾回收
1. 析构函数。
2. 实现 IDisposable 接口。
在我们编写代码时,一个简单的方法就是查看类中定义的字段是否有继承 IDisposable 接口的,如果有,那么当前的类也应继承 IDisposable 接口。在使用完非托管资源时,要及时调用 Dispose 方法释放资源:
Label label = new Label();
this.Controls.Add(label);
this.Controls.Remove(label);
label.Dispose();
更好的方式是使用 using,using 会在编译代码的时候自动创建 try/finally 语句块,在 finally 语句块中自动调用 Dispose 方法。
using (Label label = new Label())
{
this.Controls.Add(label);
this.Controls.Remove(label);
}
> 避免内存泄漏的几点建议
除了刚刚提到的非托管资源,还有几点需要注意:
1. 订阅事件,不再使用时要记得取消订阅。
2. 不要大量使用静态字段,静态字段会永远存活,一个静态的集合很容易引起内存溢出。
.NET 垃圾回收与内存泄漏的更多相关文章
- 深入理解Node.js中的垃圾回收和内存泄漏的捕获
深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...
- js垃圾回收和内存泄漏
js垃圾回收和内存泄漏 js垃圾回收 Js具有自动垃圾回收机制.垃圾收集器会按照固定的时间间隔周期性的执行. 1.标记清除(常用) 工作原理:是当变量进入环境时,将这个变量标记为"进入环境& ...
- .NET垃圾回收与内存泄漏
相信大家一定听过,看过甚至遇到过内存泄漏.在 .NET 平台也一定知道有垃圾回收器,它可以让开发人员不必担心内存的释放问题,因为它会自定管理内存.但是在 .NET 平台下进行编程,绝对不会发生内存泄漏 ...
- JavaScript中的垃圾回收和内存泄漏
摘要: JS内存管理. 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 程序的运行需要内存.只要程序提出要求,操作系统或者运行时就必须供给内存.所谓的内存泄漏简单来说是不再用到的 ...
- js垃圾回收及内存泄漏
js垃圾回收 js能够自动回收申请却未使用的内存,由于每次清除需要的性能较大,不是时时在刷新,而是每隔一段时间才进行一次. 回收的两种方式 标记清除(常用) 在内存中先标记变量,然后清除那些那些进入环 ...
- js垃圾回收与内存泄漏
js垃圾回收机制 概念: javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中,开发人员的一项基本任务就是手动跟踪内存的使用情况 ...
- 《JavaScript 闯关记》之垃圾回收和内存管理
JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存.而在 C 和 C++ 之类的语言中,开发人员的一项基本 ...
- 深入了解C#系列:谈谈C#中垃圾回收与内存管理机制
今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会 ...
- C#中垃圾回收与内存管理机制
今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会 ...
随机推荐
- 记一个mvn奇怪错误: Archive for required library: 'D:/mvn/repos/junit/junit/3.8.1/junit-3.8.1.jar' in project 'xxx' cannot be read or is not a valid ZIP file
我的maven 项目有一个红色感叹号, 而且Problems 存在 errors : Description Resource Path Location Type Archive for requi ...
- Pivot 和 Unpivot
在TSQL中,使用Pivot和Unpivot运算符将一个关系表转换成另外一个关系表,两个命令实现的操作是“相反”的,但是,pivot之后,不能通过unpivot将数据还原.这两个运算符的操作数比较复杂 ...
- [.NET] C# 知识回顾 - Event 事件
C# 知识回顾 - Event 事件 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6060297.html 序 昨天,通过<C# 知识回顾 - ...
- 免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
前面介绍了六种.NET组件,其中有一种组件是写文件的压缩和解压,现在介绍另一种文件的解压缩组件SharpZipLib.在这个组件介绍系列中,只为简单的介绍组件的背景和简单的应用,读者在阅读时可以结合官 ...
- 关于Visual Studio 未能加载各种Package包的解决方案
问题: 打开Visual Studio 的时候,总提示未能加载相应的Package包,有时候还无法打开项目,各种提示 解决方案: 进入用户目录 C:\Users\用户名\AppData\Local\M ...
- android 事件分发机制详解(OnTouchListener,OnClick)
昨天做东西做到触摸事件冲突,以前也经常碰到事件冲突,想到要研究一下Android的事件冲突机制,于是从昨天开始到今天整整一天时间都要了解这方面的知识,这才懂了安卓的触摸和点击事件的机制.探究如下: 首 ...
- 「译」JUnit 5 系列:环境搭建
原文地址:http://blog.codefx.org/libraries/junit-5-setup/ 原文日期:15, Feb, 2016 译文首发:Linesh 的博客:环境搭建 我的 Gith ...
- 烂泥:redis3.2.3安装与配置
本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb 前一段时间写过一篇codis集群的文章,写那篇文章主要是因为当时的项目不支持redis自 ...
- 微软Visual Studio Code 0.8.0发布,新增多种主题
月30日,Build 开发者大会上,正式宣布了 Visual Studio Code 项目;并将其定义为:一个运行于 Mac OS X.Windows和 Linux 之上的,针对于编写现代 Web 和 ...
- .Net中的AOP系列之《方法执行前后——边界切面》
返回<.Net中的AOP>系列学习总目录 本篇目录 边界切面 PostSharp方法边界 方法边界 VS 方法拦截 ASP.NET HttpModule边界 真实案例--检查是否为移动端用 ...