C#基础知识之GC 垃圾回收
一、托管
.Net所指的托管资源到底是什么意思呢?是相对于所有资源,还是只限于某一方面的资源?很多人对此不是很了解。
其实.Net所指的托管只是针对内存这一个方面,并不是对于所有的元素;因此对于Stream,数据库的连接GDI+的相关对象,还有Com对象等等,这些资源并不是受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC(Garbage Collector),而至于其他资源则需要手动进行释放。
二、垃圾
什么是垃圾。.Net类型分为两大类,一个就是值类型,另一个就是引用类型。前者是分配在栈上,并不需要GC回收;后者是分配在堆上,因此它的内存释放和回收需要通过GC来完成。GC的全程为"Garbage Collector",顾名思义就是垃圾回收器,那么只有被称为垃圾的对象才能被GC回收。也就是说,一个引用类型对象所占的内存需要被GC回收,而满足回收的条件,首先就要需要称为垃圾。那么.Net如果判定一个引用类型对象是垃圾呢,.Net的判断很简单,只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。
三、GC运作方式
明确了基本概念,接下来就说说GC的运作方式以及GC的功能,内存的释放和回收需要伴随着程序的运行,因此系统为GC安排了独立的线程。那么GC的工作大致是,查询内存中对象是否成为垃圾,然后对垃圾进行释放和回收。那么对于GC对于内存回收采取了一定的有限算法进行轮询回收内存资源。其次,对于内存中的垃圾分为两种,一种需要调用对象的析构函数,另一种是不需要调用的。GC对于前者(需要调用析构函数的)的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮询完成,即需要两次轮询;相对于后者(不需要调用析构函数的),则只是回收内存而已。
对于某个具体的资源,是无法确切知道对象析构函数什么时候被调用的,以及GC什么时候会去释放和回收它所占用的内存。那么对于C、C++之类语言转换过来的程序员来说,这里需要转变观念。
对于程序资源来说,我们应该做些什么,以及如何去做,才能使程序效率最高,同时占用资源能尽快的释放。前面说过托管资源分两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了,那么对于非托管资源,这里再重申一下,这就是Stream,数据库的连接,GDI+的相关对象,还有Com的相关对象,还有Com对象等等这些资源,需要我们手动去释放。
如何去释放,应该把这些操作放到哪里比较好呢。.Net提供可三种方法,也是最常见的三种,大致如下
1、析构函数
2、继承IDisposable接口,实现Dispose方法;
3、提供Close方法。
经过前面的介绍,可以知道析构函数只能被GC来调用,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄露,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占用的资源需要被标记无用了,也就是此对象被销毁了,等待GC回收,不能再被使用。
例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,大家在往自己的类中添加的接口时候,不要歪曲了这两者意思。
接下来说说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使大家的印象更深。
首先是这三种方法的实现,大致如下:
public class DisposeClass : IDisposable
{
public void Close()
{
Debug.WriteLine("Close called!");
}
~DisposeClass()
{
Debug.WriteLine("Destructor called!");
}
#region IDisposable Members
public void Dispose()
{
Debug.WriteLine("Dispose called!");
}
#endregion
}
对于Close来说不属于真正意义上的释放,除了注意它需要显示被调用外,我在此对它不多说了。而对于析构函数而言,不是在对象离开作用域后立刻被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看如下的代码运行结果。
namespace ConsoleApplication1
{
public class Program
{
static void Main(string[] args)
{
// Show destructor
new Program().Create();
Debug.WriteLine("After created!");
new Program().CallGC();
Console.ReadKey();
}
private void Create()
{
DisposeClass myClass = new DisposeClass();
}
private void CallGC()
{
GC.Collect();
}
}
public class DisposeClass : IDisposable
{
public void Close()
{
Debug.WriteLine("Close called!");
}
~DisposeClass()
{
Debug.WriteLine("Destructor called!");
}
#region IDisposable Members
public void Dispose()
{
Debug.WriteLine("Dispose called!");
}
#endregion
}
}
这时在Visual Studio的输出标签里输出:
显然在执行完Create函数外,myClass对象的析构函数没有被立刻调用,而是等显示调用GC.Collect才被调用。
对于Dispose来说,也需要显示的调用,但是对于继承了IDisposable的类型对象可以使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:
namespace ConsoleApplication1
{
public class Program
{
static void Main(string[] args)
{
using (DisposeClass myClass = new DisposeClass())
{
//other operation here
}
Console.ReadKey();
}
} public class DisposeClass : IDisposable
{
public void Close()
{
Debug.WriteLine("Close called!");
} ~DisposeClass()
{
Debug.WriteLine("Destructor called!");
}
#region IDisposable Members
public void Dispose()
{
Debug.WriteLine("Dispose called!");
}
#endregion
}
}
在Visual Studio里输出:
Dispose called!
Destructor called!
那么对于如上DisposeClass类型的Dispose实现来说,事实上GC还需要调用对象的析构函数,正常来说Dispose函数,应该通过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass如下:
public class DisposeClass : IDisposable
{
public void Close()
{
Debug.WriteLine("Close called!");
}
~DisposeClass()
{
Debug.WriteLine("Destructor called!");
}
#region IDisposable Members
public void Dispose()
{
Debug.WriteLine("Dispose called!");
//不在执行析构函数
GC.SuppressFinalize( this );
}
#endregion
}
对比表格如下:
析构函数 |
Dispose方法 |
Close方法 |
|
意义 |
销毁对象 |
销毁对象 |
关闭对象资源 |
调用方式 |
不能被显式调用,会被GC调用 |
需要显式调用 或者通过using语句 |
需要显式调用 |
调用时机 |
不确定 |
确定,在显式调用或者离开using程序块 |
确定,在显式调用时 |
那么在定义一个类型的时候,是否一定要给出这三个函数地实现呢。我的建议大致如下。
1.提供析构函数,避免资源未被释放,主要是指非内存资源;
2.对于Dispose和Close方法来说,需要看所定义的类型所使用的资源(参看前面所说),而决定是否去定义这两个函数;
3.在实现Dispose方法的时候,一定要加上“GC.SuppressFinalize( this )”语句,避免再让GC调用对象的析构函数。
注意事项:析构函数只能由垃圾回收器调用。Despose()方法只能由类的使用者调用。在C#中,凡是继承了IDisposable接口的类,都可以使用using语句,从而在超出作用域后,让系统自动调用Dispose()方法。一个资源安全的类,都实现了IDisposable接口和析构函数。提供手动释放资源和系统自动释放资源的双保险。
C#基础知识之GC 垃圾回收的更多相关文章
- Java基础知识总结之垃圾回收机制
垃圾回收机制 Java垃圾回收机制是Java语言的重要功能之一.当程序创建对象,数组等引用类型对象时,系统会自动在内存区为之分配一块内存,对象就保存在这块内存区内,当这块内存不再被任何变量引用时,这块 ...
- 数往知来C#之接口 值类型与引用类型 静态非静态 异常处理 GC垃圾回收 值类型引用类型内存分配<四>
C# 基础接口篇 一.多态复习 使用个new来实现,使用virtual与override -->new隐藏父类方法 根据当前类型,电泳对应的方法(成员) -->override ...
- GC垃圾回收算法
什么是GC垃圾回收呢.日常生活中我们去餐厅吃饭吃完饭,吃完饭走了餐具不用管,服务员在把餐具拿走,这是一种方式,服务员怎么知道他要来把餐具拿走呢,因为你走了,这个位置空了.服务员什么时候拿走餐具很重要, ...
- JVM学习02:GC垃圾回收和内存分配
JVM学习02:GC垃圾回收和内存分配 写在前面:本系列分享主要参考资料是 周志明老师的<深入理解Java虚拟机>第二版. GC垃圾回收和内存分配知识要点Xmind梳理 案例分析1-(G ...
- java面试题之----JVM架构和GC垃圾回收机制详解
JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...
- 面试官,不要再问我“Java GC垃圾回收机制”了
Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解> ...
- JVM虚拟机 与 GC 垃圾回收
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.JVM体系结构概述 1.JVM 与系统.硬件 JVM是运行在操作系统之上的,它与硬件没有直接的交 ...
- .Net平台的GC垃圾回收
一.先了解下必备的知识前提 内存中的托管与非托管,可简单理解为: 托管:可借助GC从内存中释放的数据对象(以下要描述的内容点) 非托管:必须手工借助Dispose释放资源(实现自IDisposable ...
- GC垃圾回收机制详解
JVM堆相关知识 为什么先说JVM堆? JVM的堆是Java对象的活动空间,程序中的类的对象从中分配空间,其存储着正在运行着的应用程序用到的所有对象.这些对象的建立方式就是那些new一类的操作 ...
随机推荐
- 深入浅出mysql笔记---1、mysql下载安装
深入浅出mysql笔记---1.mysql下载安装 一.总结 一句话总结: linux下rpm安装即可 1.linux的wget命令作用? 下载文件的工具:比如wget http://cn.wordp ...
- GTX 1060 3GB 能否使用DeepFaceLab ?
大部分人都知道跑换脸软件对电脑配置的要求比较高.所以当你想要开始玩之前都会有一个疑问:我的电脑能跑起来了么?或者我的电脑能跑那个模型? 之前写过一篇750 1G显卡如何玩deepfakes的文章.今天 ...
- getBoundingClientRect 和 requestAnimFrame 的polyfill
概述 今天在项目中用到了 getBoundingClientRect 和 requestAnimFrame ,查了下它们的polyfill,记录下来,供以后开发时参考,相信对其他人也有用. getBo ...
- 阶段3 1.Mybatis_01.Mybatis课程介绍及环境搭建_05.mybatis环境搭建-前期准备
视频中右侧没有勾选 直接finish 用下面的sql里面的一些表来实现今天的功能 只需要用到里面的user表. 这是之前已经建好的数据库 把表都删除掉,用sql语句去创建表和表内的记录,最终的结果: ...
- python学习笔记:(四)tuple(元组)常用方法
tuple(元组)的常用方法 1.del 删除元组 #del 删除元组 a=(1,2,3) del a print(a) 2.len() 计算元组中,值的个数 #len:计算元组元素的个数 a=(1, ...
- keepalive + nginx 搭建高可用集群动态网站
环境准备: 两台节点部署keepalived,并且设为互为主从,实现高可用. 两台从节点部署nginx以及相关组件,作为真实服务器实现动态网站上线. 一.MASTER(BACKUP)节点下载keepa ...
- delphi2010:按键 控制键 组合键的判断 响应
procedure TForm7.FormShortCut(var Msg: TWMKey; var Handled: Boolean); var aKey: TShortCut; aShif ...
- 设置了responseType:Blob之后,如果返回json错误信息,如果获取?
最近做了一个文件下载功能,于是设置了responseType: Blob的方式, 什么是Blob呢,MDN官方解释:Blob 对象表示一个不可变.原始数据的类文件对象.Blob 表示的不一定是Java ...
- SQLSERVER 和 ORACLE 查询数据库文件大小
SQLSERVER: SQLSERVER一个库的文件分为数据文件(行数据)和日志文件两个文件,详情可以在数据库的属性->文件中查看. 在资源管理器中打开文件所在路径可以直接看到这两个文件 但是, ...
- 【BW系列】SAP BW实时抽取ECC数据的实现
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[BW系列]SAP BW实时抽取ECC数据的实现 ...