菜鸟之旅——.NET垃圾回收机制
.NET的垃圾回收机制是一个非常强大的功能,尽管我们很少主动使用,但它一直在默默的在后台运行,我们仍需要意识到它的存在,了解它,做出更高效的.NET应用程序;下面我分享一下我对于垃圾回收机制(GC)的学习心得。
GC的必要性
我们知道程序会需要向内存堆使用new请求内存,然后将请求的内存初始化并使用,使用完毕之后,变清理资源和释放内存,等待别的程序来请求使用;对内存资源的管理方式,现在存在这么几种管理方式:
1、手动管理:C、C++
2、计数管理:COM
3、自动管理:.NET、JAVA、PHP
现在的高级语言基本上都实现了自动管理内存,这是因为手动管理内存会因为人为的原因产生以下问题:
1、开发人员忘记释放请求的内存,造成内存泄漏,若是内存泄露过多,则可能会造成内存溢出,导致程序无法运行;
2、应用程序访问已释放的内存,造成数据读取错误。
由此可见,手动去管理堆里面的内存可靠程度,会因开发人员的不同而不同,在C++因指针而出现的问题可不少;而且易出现Bug等乱七八糟的问题,影响系统稳定性,所以自动化管理内存是必要的。
GC的工作原理
通用概念
回收时机
当应用程序分配新的对象,GC的代的预算大小已经达到阈值,比如GC的第0代已满;
代码主动显式调用System.GC.Collect();
其他特殊情况,比如,windows报告内存不足、CLR卸载AppDomain、CLR关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收。
应用程序根
应用程序根(application root):根(root)就是一个存储位置其中保存着对托管堆上一个对象的引用,根可以属性下面任何一个类别
- 全局对象和静态对象的引用
- 应用程序代码库中局部对象的引用
- 传递进一个方法的对象参数的引用
- 等待被终结(finalize,后面介绍)对象的引用
- 任何引用对象的CPU寄存器
代
垃圾回收器将托管堆(heap)里面的对象划分为3个代(一般为3代),可以使用GC.MaxGeneration()方法来进行查询当前系统所支持的最大代数:
1、G0 小对象(Size<85000Byte):新分配的小于85000字节的对象
2、G1:在GC中幸存下来的G0对象
3、G2:大对象(Size>=85000Byte);在GC中幸存下来的G1对象
当一个对象被new的时候,它的代为0,经过一次回收之后,若该对象没有被回收,则代上升,变为1,若每次回收都幸存下来,则代都会上升,最大代为操作系统所支持的最大代。
因为将对象以代划分,并且可以单独回收某一个世代,避免回收整个托管堆,提升性能。一个基于代的垃圾回收器有一下特点:
1、对象越新,生存期越短;
2、对象越老,生存期越长;
3、回收堆的一部分,速度快于回收整个堆。
工作过程
标记对象
在垃圾回收的第一步就是标记对象:垃圾回收器会认为托管堆中的所有对象都是垃圾,然后垃圾回收器会去检查所有的应用程序根,遍历每个根所引用到的对象,将其标记为活动的(live ),所有的根对象都检查完之后,有标记的对象就是可达对象,未标记的对象就是不可达对象,不可达对象就是回收的目标。
弱引用对象则不在考虑范围之内,所以一定会被回收掉的。
销毁对象,释放内存
在经过第一步的对象筛选之后,回收没有被引用的对象,就是不可达对象,GC调用对象默认的终结器Finalize(),销毁对象之后,将内存也释放掉。
同时,还存在引用的对象,就是可达对象的世代变为下一个世代。
压缩堆内存
经过第二步的销毁对象和释放内存之后,幸存下来的对象在堆中的排列可能是不连续的,这时在堆中存在非常多的内存碎片,程序在new对象的时候都是请求一段连续的内存,则内存碎片可能就无法再次利用(虽然没有被使用),造成内存资源的浪费,所以垃圾回收的最后一步就是压缩内存:将垃圾回收后幸存的对象移动到一起,并且将各个对象的引用更新到对象新的位置上,保证对象引用的正确性。

注:从这里看得出,在压缩堆内存的时候,所有相关线程必须暂停,因为压缩时不能保证对象引用的正确性,所以在垃圾回收的时候,GC会劫持所有相关线程,在回收完毕之后,被劫持的线程才会正常工作,所以垃圾回收势必会影响一定的性能,所以慎用System.GC.Collect()。
Finalize()与Dispose()
上面说到,GC在回收对象的时候是调用对象的终结器Finalize()来实现的,那么,就简单的总结一下Finalize()与Dispose()吧:
1、调用者:
Finalize只能由GC调用
Dispose由开发人员显示调用,也可以使用use区块,在程序离开区块使自动调用Dispose方法
2、调用时机:
Finalize由于是GC调用的,所以调用时机是垃圾回收的时候调用,时机不确定
Dispose由于是显示调用,所以调用时机是确定的,在调用方法的时候就调用了
3、目的:
这里的目的主要说是Dispose出现的目的;
首先是.NET存在托管资源和非托管资源,一般来说,非托管资源数量有限,比较珍贵,在使用完毕之后,希望能够释放掉,那么将释放非托管资源的方法写到终结器Finalize里面也是可以的,但是由于Finalize的调用时机不确定,导致释放资源不及时,那么有限的非托管资源很快就被占用完毕,所以,为了能够及时的释放掉这类资源,我们需要能够显示调用的方法,这就是Dispose。
Finalize主要是为了GC释放托管资源和销毁对象,释放内存
Dispose主要是为了释放托管和非托管资源和销毁对象,释放内存
注:不必担心资源的重复释放问题,就算是重复释放,.NET也做好了相应措施来处理,不会抛出异常。
下面贴一个MSDN推荐的标准的Dispose实现方式
class Class : IDisposable
{
// 标识:是否释放托管资源
private bool disposed = false; // 显示调用的方法
public void Dispose()
{
Dispose(true);
// 将对象从垃圾回收器链表中移除,
// 从而在垃圾回收器工作时,只释放托管资源,而不执行此对象的析构函数
GC.SuppressFinalize(this);
} // 受保护的释放资源方法
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
// 此处写释放托管资源的方法
}
disposed = true; // 此处写释放非托管资源的方法
}
}
~Class()
{
// 这里是防止忘记显示调用Dispose(),在GC进行垃圾回收的时候进行释放非托管资源
Dispose(false);
}
}
总结
GC所带来的便利是不言而喻的,但是这是付出一定的系统性能来实现的:在垃圾回收的时候GC会劫持所有相关的线程,并且会有一定的时空开销,所以在平时开发过程中注意一些良好的开发习惯可能会对GC有一些积极的影响。
1、尽量不要new很大的对象,大对象(>=85000Byte)直接归为G2代,GC回收算法从来不对大对象堆(LOH)进行内存压缩整理,移动大对象将会消耗更多的CPU时间,也更容易造成内存碎片。这里也可以将大对象或者生命周期长的对象进行池化。
2、不要频繁的new生命周期短的小对象,这可能会导致频繁的垃圾回收,这里可以考虑使用结构体放在栈中来代替,或者也可以使用对象池化来优化。
3、不推荐使用对象池化的解决方案,它比较笨重和容易出错,设计一个高性能稳定的对象池并不容易。
4、降低对象之间的纵向深度,GC在回收过程中,会先顺着根来进行对象遍历和标记,减少深度可以加快遍历速度;若系统中各个类之间的关系错综复杂,那么考虑一下设计方案是否合理。
当然注意的地方还有不少,最后贴一篇博客,这里介绍了如何编写高性能的.NET代码,其中的GC介绍非常详细:
菜鸟之旅——.NET垃圾回收机制的更多相关文章
- PHP内核之旅-6.垃圾回收机制
回收PHP 内核之旅系列 PHP内核之旅-1.生命周期 PHP内核之旅-2.SAPI中的Cli PHP内核之旅-3.变量 PHP内核之旅-4.字符串 PHP内核之旅-5.强大的数组 PHP内核之旅-6 ...
- Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解
http://blog.csdn.net/yueguanghaidao/article/details/11274737
- 浅谈V8引擎中的垃圾回收机制
最近在看<深入浅出nodejs>关于V8垃圾回收机制的章节,转自:http://blog.segmentfault.com/skyinlayer/1190000000440270 这篇文章 ...
- 前端面试:谈谈 JS 垃圾回收机制
摘要: 不是每个人都回答的出来... 最近看到一些面试的回顾,不少有被面试官问到谈谈JS 垃圾回收机制,说实话,面试官会问这个问题,说明他最近看到一些关于 JS 垃圾回收机制的相关的文章,为了 B 格 ...
- php的垃圾回收机制
转载请附上本文地址:http://blog.csdn.net/u011957758/article/details/76864400 前言 是的,平时经常听到大牛说到的gc,就是垃圾回收器,全称Gar ...
- 一看就懂系列之 由浅入深聊一聊php的垃圾回收机制
前言 是的,平时经常听到大牛说到的gc,就是垃圾回收器,全称Garbage Collection. 早期版本,准确地说是5.3之前(不包括5.3)的垃圾回收机制,是没有专门的垃圾回收器的.只是简单的判 ...
- 图解 Java 垃圾回收机制,写得非常好!
阅读本文大概需要 3.7 分钟. 翻译:Rhys_Lee, AzureSora, 溪边九节, 小小菜鸟鸡 blog.csdn.net/zl1zl2zl3/article/details/9090408 ...
- .net垃圾回收机制编程调试试验
1. 什么是CLR GC? 它是一个基于引用跟踪和代的垃圾回收器. 从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被 ...
- JavaScript具有自动垃圾回收机制
JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存. 正常的生命周期: 局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...
随机推荐
- MySQL浅谈 LEFT JOIN
On条件(在“A left join b on conditional_expr”)决定如何从table B 中检索数据行(Matching-State); 如果B中没有行匹配On 条件,额外的B的所 ...
- 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)
原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...
- RocketMQ-Tomcat中部署rocketmq-console
1. https://github.com/didapinchegit/rocket-console 在github上下载rocketmq-consoler的源码, 用mvn clean packag ...
- mybatis用spring的动态数据源实现读写分离
一.环境: 三个mysql数据库.一个master,两个slaver.master写数据,slaver读数据. 二.原理: 借助Spring的 AbstractRoutingDataSource 这个 ...
- python_10_文件操作
文件操作逻辑? 打开文件,赋值给一个对象 用对象操作文件 关闭文件 如何打开文件? 在windows中,默认格式gbk,python3.x默认unicode(utf-8),要指定编码值 语法: f = ...
- Switch-case 内定义变量的问题
Switch-case 内定义变量的问题 这个问题需要分开讨论,C 语言和 C++ 的标准定义是不同的. C++ int Caset(int a) { switch (a) { case 1: int ...
- junit的意义
写了这么久代码了,自己从来没有好好的玩过junit.马上过年了,打算趁这段时间自己来写一套web框架,但是这里有一个很大的尴尬就是我平时编码并没有认真的来写测试类.那么自己在写框架的时候,不测试肯定是 ...
- Servlet--取得初始化配置信息
关于这块内容,主要就是玩一个接口:ServletConfig.先翻下API,了解一下. 定义: public interface ServletConfig 这个接口定义了一个对象,通过这个对象,Se ...
- JAVA中文件与Byte数组相互转换的方法
JAVA中文件与Byte数组相互转换的方法,如下: public class FileUtil { //将文件转换成Byte数组 public static byte[] getBytesByFile ...
- WPF笔记(2.4 Grid)
第一章已经简单介绍过这个容器,这一节详细介绍.Grid一般是用表格(Grid.Row 和Grid.Column )的,比StackPanel更细致一些,但是,这么玩很麻烦,先横着竖着定义一大堆,然后把 ...