菜鸟之旅——.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具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存. 正常的生命周期: 局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...
随机推荐
- ajax和跨域
一.简介 ajax是什么? AJAX(Asynchronous JavaScript and XML) 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. AJAX, (异步的JavaSc ...
- JavaScript学习日志:关于js分号
javascript有自动添加分号的功能,但是不是所有情况都会自动添加,要区分: 1,如果语句独占一行 如果当前行内的语句能够被js正确解析,那么就会在句尾添加一个分号. (如何判断是否正确解析?你在 ...
- char,varchar,nvarchar,text区别与联系
CHAR,NCHAR 定长,速度快,占空间大,需处理VARCHAR,NVARCHAR,TEXT 不定长,空间小,速度慢,无需处理NCHAR.NVARCHAR.NTEXT处理Unicode码
- express官网学习笔记
npm init 创建一个package.json npm install express --save-dev 安装到项目依赖 便于多人开发 路由结构定义 app.METHOD(PATH, HAND ...
- google的grpc在golang中的使用
GRPC是google开源的一个高性能.跨语言的RPC框架,基于HTTP2协议,基于protobuf 3.x,基于Netty 4.x. 前面写过一篇golang标准库的rpc包的用法,这篇文章接着讲一 ...
- Using $this when not in object context in
错误信息:$this引用没有上下文 原因:在PHP5中,static声明的静态方法里不可以使用$this 需要使用self来引用当前类中的方法或是变量. 引用的方法里不可以带$this(示例代码中为g ...
- 锋利的jQuery笔记
首先分清jQuery对象和DOM对象,这两者可相互转化,如: var $cr=$("#cr"); //jquery对象 var cr=$cr[0] ; //DOM对象 var cr ...
- MySQL数据库学习笔记----MySQL多表查询之外键、表连接、子查询、索引
本章主要内容: 一.外键 二.表连接 三.子查询 四.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复 ...
- 文字滚动效果,jquery和marquee标签
链接:https://pan.baidu.com/s/1pMwHYH1 密码:r9ys marquee标签是微软创建的,后来大部分浏览器都适用后,微软在IE8把这个标签去掉了.为符合W3C规范,还是使 ...
- 【原创】Struts2.5.12版本中使用通配符*
ι 版权声明:本文为博主原创文章,未经博主允许不得转载. <package name="hellodemo" extends="struts-default&quo ...