谈一谈.net析构函数对垃圾回收的影响
之前忘了说了 代码都是在Release模式下运行的,现在补充上。
这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样。析构函数编译成il语言后会变成一个Finalize的函数,他是重写的object的Finalize虚函数,标题上用析构函数,主要是我认为很多人不知道Finalize函数。
写一个类型解释下可能会更通俗易懂一点:
public class Test
{
~Test() { } //这个就是Finalize函数
private byte[] b = new byte[];
}
最近看了一些代码,有不少用Finalize函数的。特别是ef数据仓库中,情况如下:
public class DbRepostory
{
private Context context;
public DbRepostoty(Context context)
{
this.context = context;
}
~DbRepostory()
{
context.Dispose();
}
}
public class Context : DbContext
{
}
看上去很高大上,但是这样写到底好不好呢?好不好我们最后再去评论,先看一看下面这个简单的例子:
public class WithFinalize
{
~WithFinalize() { }
private byte[] b = new byte[];
}
public class WithoutFinalize
{
private byte[] b = new byte[];
}
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();
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = ; i < ; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
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();
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = ; i < ; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
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析构函数对垃圾回收的影响简析
这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样.析构函数编译成il语言后会变成一 ...
- 浅谈Chrome V8引擎中的垃圾回收机制
垃圾回收器 JavaScript的垃圾回收器 JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带 ...
- 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 ...
- 浅谈JVM垃圾回收
JVM内存区域 要想搞懂啊垃圾回收机制,首先就要知道垃圾回收主要回收的是哪些数据,这些数据主要在哪一块区域. Java8和Java8之前的相同点有很多. 都有虚拟机栈,本地方法栈,程序计数器,这三个是 ...
- AJPFX浅谈Java 性能优化之垃圾回收(GC)
★JVM 的内存空间 在 Java 虚拟机规范中,提及了如下几种类型的内存空间: ◇栈内存(Stack):每个线程私有的.◇堆内存(Heap):所有线程公用的.◇方法区(Method Area):有点 ...
- 【深入Java虚拟机】之二:Java垃圾回收机制
[深入Java虚拟机]之:Java垃圾收集机制 对象引用 Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了Java中所有的对象实例.谈到Java堆中的垃圾回收,自然要谈到引用.在JDK ...
随机推荐
- Lesson 1 A private conversation
Text Last week I went to the theatre. I had a very good seat. The play was very intersting. I did no ...
- vmware 安装xp 流水账
1. 分区 PQ分区.1个区,C盘,NTFS. 2. 安装XP 进入ghost,不要选择一键. 然后fromImage, d:\xxx\GHO
- php的函数和数组
//php函数//1.简单函数//四要素:返回类型,函数名,参数列表,函数体/*function Show(){ echo "hello";}Show();*///2.有返回 ...
- jquery中on/delegate的原理
jquery中on/delegate的原理 早期版本中叫delegate, 后来有过live函数,再后来统一用on.下面的方法等效: // jQuery 1.3 $(selector).(events ...
- jarsigner签名报错Invalid keystore format
由于之前在魅族市场的APK包都不是自己上传的,而是魅族从其他安卓市场帮拉去过来了. 所以需要我们自己去认领APK包. 这个时候就需要按照魅族给的未签名测试包给重新签名然后提交审核了. 1:看完以下说明 ...
- 使用Ado.net执行SP很慢,而用SSMS执行很快
今天遇到一个问题,有用户反应,在site上打开报表,一直loading,出不来结果. 遇到这种问题,我立刻simulate用户使用Filter Condition,问题repro,看来不是偶然事件,通 ...
- 没有Hyper-V服务,WP Emulator无法启动
解决: 打开管理工具
- jQuery 2.0.3 源码分析core - 整体架构
拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery ...
- jQuery源码分析系列(39) : 动画队列
data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保 ...
- 解决HubbleDotNet搜索引擎索引数据不全的问题
HubbleDotnet是国产.NET平台搜索引擎的翘楚,开放源代码,使用方便,不过我一直在非生产环境下使用.官方网页在HubbleDotNet开源全文搜索数据库项目--技术详解. 以前当数据库使用M ...