.net析构函数对垃圾回收的影响简析
这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样。析构函数编译成il语言后会变成一个Finalize的函数,他是重写的object的Finalize虚函数,标题上用析构函数,主要是我认为很多人不知道Finalize函数。
写一个类型解释下可能会更通俗易懂一点:
public class Test
{
~Test() { } //这个就是Finalize函数
private byte[] b = new byte[10000];
}
最近看了一些代码,有不少用Finalize函数的。特别是ef数据仓库中,情况如下:
public class DbRepository
{
private Context context;
public DbRepository(Context context)
{
this.context = context;
}
~DbRepository()
{
context.Dispose();
}
}
public class Context : DbContext
{
}
看上去很高大上,但是这样写到底好不好呢?好不好我们最后再去评论,先看一看下面这个简单的例子:
public class WithFinalize
{
~WithFinalize() { }
private byte[] b = new byte[10000];
}
public class WithoutFinalize
{
private byte[] b = new byte[10000];
}
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("测试1无Finalize函数:");
Test<WithoutFinalize>();
Console.WriteLine(Environment.NewLine+ "测试2有Finalize函数:");
Test<WithFinalize>(); Console.ReadKey();
}
public static void Test<T>() where T : new() //约束,可以参照
{
GC.Collect();
Thread.Sleep(10);
//Finalize方法的调用和我们的前台代码是并发进行的,
//如果不暂停一下的话很可能不少对象的Finalize方法还没执行完,
//就调用了下一次的垃圾回收(GC.Collect())。影响结果的准确性,因此暂停一会
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = 0; i < 10; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep(10);
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep(10);
Console.WriteLine("二次回收:" + GC.GetTotalMemory(false));
}
}
这段代码有三个类一个是我们需要运行的主程序,另外两个 WhitFinalize 和WhitoutFinalize则是我们要测试的类型,这两个类一个加了Finalize函数,一个未加,其余的完全一样。主程序则分别要测试这两个类型在垃圾回收的时的表现,我们先测试的没有加Finalize函数的类型,在测试的加了类型。 一共四个数值,分别是初始时的内存, new了10个测试类型之后的内存(测试类型大约需要10k的内存空间,10个也就是大约100k),垃圾回收一次之后的内存,垃圾回收二次之后的内存,我们看下具体的运行情况:
测试1无Finalize函数:
初始内存:96224
分配之后:196464
一次回收:97036
二次回收:97036 测试2有Finalize函数:
初始内存:97056
分配之后:197296
一次回收:197396
二次回收:97156
从运行情况来看两次测试的初始化内存都大约97k左右,new了10个测试对象之后都增长了大约100k,和预期的一样,但是第一次垃圾回收之后测试1(没有Finalize函数)回收了100k左右的内存,而测试2(有Finalize函数)则基本上没有回收掉内存,却等到了第二次垃圾回收 回收了100k内存。不禁会想,这又是为什呢?
这得从垃圾回收的一些原理说起,东西比较多,我们说的简单一下。垃圾回收的时候会从根遍历所有引用的对象,然后遍历到了就做好标记,代表有用,没遍历到的就会是为垃圾,但是在这些垃圾中有一些对象定义了Finalize函数,于是就把这些有Finalize的对象从垃圾堆里拉了回来,其余的垃圾则回收掉,而这些死而复活的对象则和那些本来就不是垃圾对象都幸存了下来,并一并升级为下一代对象,垃圾回收结束之后 clr会用一个较高优先级的线程来调用这些死而复活对象的Finalize方法,直到下次垃圾回收他们才被回收掉。这也是我们看到测试2第二次垃圾回收才被回收掉的原因,我们在这里讲的都是一些粗略的东西,内部实现还要复杂。
我们看到我在代码里用到了很多Thread.Sleep(10); 这是什么原因呢?这就的注意下我上一段的一句话“垃圾回收结束之后 clr会用一个较高优先级的线程来调用这些死而复活对象的Finalize方法”,Finalize方法的调用和我们的前台代码是并发进行的,而且我们前台代码比较简单,如果不暂停一下的话很可能不少对象的Finalize方法还没执行完,我们就调用了下一次的垃圾回收(GC.Collect())。影响结果的准确性。
还有我们之前提到了代的概念,这里也简单说一下代,垃圾回收时对象一共有三代 :0,1,2。每一代都有自己的内存预算,空间不足的时候会调用垃圾回收。为了提高性能都是按代回收,第0代超预算之后就回收第0代的对象,而存活下来的对象就提升为第1代,依次类推,而往往经过多次0代的垃圾回收才能回收一次第1代。
我们代码中的GC.Collect();没有参数,意思是回收所有代的对象,我们可以把GC.Collect()换成GC.Collect(0);意思是回收第0代的对象,然后运行程序:
public static void Test<T>() where T : new()
{
GC.Collect();
Thread.Sleep(10);
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = 0; i < 10; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect(0);
Thread.Sleep(10);
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect(0);
Thread.Sleep(10);
Console.WriteLine("二次回收:" + GC.GetTotalMemory(false));
}
测试1无Finalize函数:
初始内存:96224
分配之后:196464
一次回收:97056
二次回收:97036 测试2有Finalize函数:
初始内存:97056
分配之后:197296
一次回收:197396
二次回收:197396
我们看到测试2中在第二次垃圾回收之后(对第0代)内存依旧没有回收掉,而这种情况更接近于实际。
从上面的小例子中我们了解到Finalize方法对性能和内存都有不好的影响,那为什么要存在这个方法呢?这里我们说一下要使用Finalize的两个情况:
第一个情况就是对象含有一个本机资源,比如一个句柄,这样可以在Finalize方法释放这个句柄,就能消除忘记释放句柄造成的本机资源浪费。
第二种情况就是在这个对象被回收之前需要做一些必须要做的是事情,比如FileStream这个类,需要在回收之前把缓冲区的东西写入到文件内。
我们在回过头开看一看之前提到的数据仓库的类,这个类第一没有占用任何本机资源,第二在被回收之前也没有必须要做的事情,写一个Finalize方法并调用 context.Dispose(); 只能增加性能开销,影响垃圾回收效果。我们可以用反编译软件看一下DbContext这个基类,他都没有Finalize方法,所以没有必要这么做了。
.net析构函数对垃圾回收的影响简析的更多相关文章
- net析构函数对垃圾回收的影响
net析构函数对垃圾回收的影响 之前忘了说了 代码都是在Release模式下运行的,现在补充上. 这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数 ...
- 谈一谈.net析构函数对垃圾回收的影响
之前忘了说了 代码都是在Release模式下运行的,现在补充上. 这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassNam ...
- C#析构函数与垃圾回收
析构函数基本语法 C# class Car { ~ Car() // destructor { // cleanup statements... } } 析构函数说明 不能在结构中定义析构函数.只能对 ...
- PHP析构函数与垃圾回收
析构函数:当某个对象成为垃圾或者当对象被显式销毁时执行. GC (Garbage Collector) 在PHP中,没有任何变量指向这个对象时,这个对象就成为垃圾.PHP会将其在内存中销毁.这是PHP ...
- 局部变量表中Slot复用对垃圾回收的影响详解
看两段代码 1. package com.jvm; public class Test { public static void main(String[] args) { { byte[] plac ...
- KingbaseESV8R6垃圾回收受到参数old_snapshot_threshold的影响
垃圾回收影响因素 影响垃圾回收的因素有很多,垃圾回收不及时,最直接导致表膨胀,详情查看文档<KingbaseESV8R6 垃圾回收原理以及如何预防膨胀>. vacuum回收垃圾的tuple ...
- C#内存管理和垃圾回收机制
数据类型 垃圾回收机制 一.数据类型 C#中的数据类型分为值类型 (Value type) 和引用类型(reference type), 值 类 型: 所有的值类型都集成自 System.Value ...
- Unity优化方向——优化Unity游戏中的垃圾回收(译)
介绍 当我们的游戏运行时,它使用内存来存储数据.当不再需要该数据时,存储该数据的内存将被释放,以便可以重用.垃圾是用来存储数据但不再使用的内存的术语.垃圾回收是该内存再次可用以进行重用的进程的名称. ...
- 小师妹学JVM之:GC的垃圾回收算法
目录 简介 对象的生命周期 垃圾回收算法 Mark and sweep Concurrent mark sweep (CMS) Serial garbage collection Parallel g ...
随机推荐
- build-essential
- WPF 中,如何使用自定义的resources
第一步,先自己自定义一个Resources 1.新建一个xaml文件,在其中自定义好自己的Resources 这个Resource 的根节点是 <ResourceDictionary xmlns ...
- [.Net MVC] 使用 log4net 日志框架
项目:后台管理平台 意义:项目开发中提出增加日志功能,对关键的操作.程序运行中的错误信息进行记录,这对程序部署后的调试有很大意义. 注:本文只是对网上搜集的信息进行了整合,以备今后查询. 关键字:.N ...
- SQL 不同的数据类型
SQL 不同的数据类型 1.SQL TEXT 2.SQL VARCHAR(SIZE) VARCHAR(X) Case: user name, email, country, subject, pass ...
- 【转】C#中没有id 没有name C#怎么点击按钮
HTML按钮元素 <input type="submit" value="确定" class="dialogbtn" C# 执行代码 ...
- json 数字key json 数字作为主键
但是当key的值为数字时,只能使用类似数组下表的访问方式取值. var json = '{"0":"a", "1":"b" ...
- RHEL 6.4 64bit kettle5.01导入xlsx格式的excel时报错
环境:RHEL 6.4 64bit : kettle5.01:xlsx格式的excel 创建的job,在spoon里面运行都没有问题(Linux和windows) 在windows的命令行运行也没有问 ...
- Python设计模式——观察者模式
需求:员工上班在偷偷看股票,拜托前台一旦老板进来,就通知他们,让他们停止看股票. 这里有两类人,一类是观察者,即员工,一类是通知者,即前台,员工在观察前台的状态,前台负责通知员工最新的动态. #enc ...
- Python设计模式——代理模式(Proxy)
书中的例子是:男A喜欢女A,但是不敢向其表白,所以委托男B为代理,代他送礼物给女A,实现这个需求的重点是,男A和女A是不互相直接接触的,都是通过代理男B,实现间接接触. #encoding=utf-8 ...
- CoreBluetooth - 中心模式
BLE中心模式流程-coding BLE中心模式流程 - 1.建立中心角色 - 2.扫描外设(Discover Peripheral) - 3.连接外设(Connect Peripheral) - 4 ...