来源:Sban .Net http://www.cnblogs.com/sban/archive/2007/12/02/978737.html
对于.Net CLR的垃圾自动回收,这两日有兴致小小研究了一下。查阅资料,写代码测试,发现不研究还罢,越研究越不明白了。在这里sban写下自己的心得以抛砖引玉,望各路高手多多指教。
近日浏览Msdn2,有一段很是费解,引于此处:
原文:http://msdn2.microsoft.com/zh-cn/library/0s71x931(VS.80).aspx
英文:把zh-cn替成en-us。此文档对应.net2.0,把VS.80替成VS.90可查看.net3.5最新文档。两者无甚差别,可见自.net1.1之后,垃圾回收机制没有改变。
据上文引用,关于GC的二次回收,sban作图一张,如下:
为了验证GC对含有终结器对象的两次回收机制,我写了一个例子测试,代码如下:
using System.Threading;
using System.IO;
using System.Data.SqlClient;
using System.Net;
namespace Lab
{
class Log
{
public static readonly string logFilePath = @" d:/log.txt " ;
public static void Write( string s)
{
Thread.Sleep( 10 );
using (StreamWriter sw = File.AppendText(logFilePath))
//此处有可能抛出文件正在使用的异常,但不影响测试
{
sw.WriteLine( " {0}/tTotalMilliseconds:{1}/tTotalMemory:{2} " , s,
DateTime.Now.TimeOfDay.TotalMilliseconds, GC.GetTotalMemory( false ));
sw.Close();
}
}
}
class World
{
protected FileStream fs = null ;
protected SqlConnection conn = null ;
public World()
{
fs = new FileStream(Log.logFilePath, FileMode.Open);
conn = new SqlConnection();
}
protected void Finalize()
{
fs.Dispose();
conn.Dispose();
Log.Write( " World's destructor is called " );
}
}
class China : World
{
public China()
: base ()
{
}
~ China()
{
Log.Write( " China's destructor is called " );
}
}
class Beijing : China
{
public Beijing()
: base ()
{
}
~ Beijing()
{
Log.Write( " Beijing's destructor is called " );
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
namespace Lab
{
class Program
{
static void Main( string [] args)
{
TestOne();
Log.Write( " In Main../t/t " );
}
static void TestOne()
{
new Beijing();
GC.Collect();
GC.WaitForPendingFinalizers();
Log.Write( " In TestOne../t/t " );
}
}
}
F5执行一下,返回如下结果:
China ' s destructor is called TotalMilliseconds:53009857.4528 TotalMemory:717428
World ' s destructor is called TotalMilliseconds:53009867.4672 TotalMemory:733812
In TestOne.. TotalMilliseconds: 53009877.4816 TotalMemory: 758388
In Main.. TotalMilliseconds: 53009887.496 TotalMemory: 783056
Beijing's destructor is called TotalMilliseconds:53589020.248 TotalMemory:697744
China's destructor is called TotalMilliseconds:53589030.2624 TotalMemory:714128
World's destructor is called TotalMilliseconds:53589040.2768 TotalMemory:738704
In TestOne.. TotalMilliseconds:53589050.2912 TotalMemory:763280
In Main.. TotalMilliseconds:53589060.3056 TotalMemory:779664
注:重点看时间与内存变化,以下同。
WaitForPendingFinalizers()相当于join了终结器队列执行线程。派生类Beijing及其父类的终结器确实已经成功执行,但是为什么内存占用却不降反升?结合两次结果来看,垃圾回收器真正释放内存应该是在退出当前应用程序域之后发生的。msdn2中有云:
看来msdn此言非虚
但是为什么内存没有真正被GC回收呢?World的终结器既已执行,其中fs.Dispose()与conn.Dispose()也得以成功执行,为什么就连微软鼓励使用的Dispose()也不好使了呢?是fs与conn对象不占内存,差别微乎其微吗?为了验证是与不是,把上文例码中的fs与conn的相关定义及初始化代码一并去掉。再运行一下:
China's destructor is called TotalMilliseconds:54514100.448 TotalMemory:582508
World's destructor is called TotalMilliseconds:54514110.4624 TotalMemory:598892
In TestOne.. TotalMilliseconds:54514120.4768 TotalMemory:623468
In Main.. TotalMilliseconds:54514130.4912 TotalMemory:639852
Beijing's destructor is called TotalMilliseconds:56343741.3424 TotalMemory:563252
China's destructor is called TotalMilliseconds:56343751.3568 TotalMemory:579636
World's destructor is called TotalMilliseconds:56343761.3712 TotalMemory:596020
In TestOne.. TotalMilliseconds:56343771.3856 TotalMemory:620596
In Main.. TotalMilliseconds:56343781.4 TotalMemory:636980
内存占用明显减少,看样子没有冤枉GC。让它回收,它确实没有给我干活啊。
在C#中,如果一个自定义类没有构造器,编译器会添加一个隐藏的无参构造器。但是析构函数不会自动创建。一旦析构函数创建了,终结器也便自动产生了。构构函数其实等同于如下代码:
Finalize();
}finally{
base.Finalize();
}
在上文代码中,World类虽没有析构函数,也被派生类China的析构触发得以执行。
如果在派生类中不存在析造函数,却重载了基类的终结器,如下:
垃圾回收时,GC找不到构造函数,会直接调用终结器。因终结器已重写,如果在该终结器中不得调用基类的终结器,那么GC将忽略基类。可以利用这个特性写一个不受垃圾回收器管辖的类,以实现某种特殊的效果。此乃旁边左道,与高手见笑了。
对于上文代码,如果把TestOne函数改成如下:
{
Beijing bj = new Beijing();
GC.Collect();
Log.Write( " In TestOne../t/t " );
}
运行一下,GC貌似无用,bj及其父类的析构函数依然在Log.Write("In TestOne...")之后执行,有无WaitForPendingFinalizers()无甚差别。但如果只new一下对象,并不赋值于变量,code如下:
{
new Beijing();
GC.Collect();
Log.Write( " In TestOne../t/t " );
}
运行结果如下:
In TestOne.. TotalMilliseconds:59773883.6448 TotalMemory:586612
China's destructor is called TotalMilliseconds:59773893.6592 TotalMemory:602996
In Main.. TotalMilliseconds:59773893.6592 TotalMemory:619380
World's destructor is called TotalMilliseconds:59773903.6736 TotalMemory:635764
Beijing's destructor is called TotalMilliseconds:59775696.2512 TotalMemory:561080
China's destructor is called TotalMilliseconds:59775706.2656 TotalMemory:577464
In TestOne.. TotalMilliseconds:59775706.2656 TotalMemory:602040
World's destructor is called TotalMilliseconds:59775716.28 TotalMemory:618424
In Main..
不用WaitForPendingFinalizers()也触发了Beijing及其父类的析构函数。GC.Collect是异步调用,该问一代而过。至于Log.Write执行的先后,要看谁能获得log文件操作句柄。上文写log文件的代码有问题,多线程应用中可能引发文件正在使用的异常,实际应用中应先申请文件句柄,申请成功lock之后方可操作。由于Write方法中让线程沉睡了10毫秒,故GC在此空档内有机会获得了文件操作句柄。
有两种情况GC都是可以触发对象的析构函数的:
1,如前面所说,在退出当前应用程序域时。
2,当对象不能再被访问时。若只是new一个对象,转行便满足条件。
对于GC.Collect,有两个版本:
1,GC.Collect();
2,GC.Collect(int32);参数为Generatio。什么是Generation?
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
namespace Lab
{
class Program
{
static void Main( string [] args)
{
TestObject obj = new TestObject();
int generation = 0 ;
for ( int j = 0 ; j < 6 ; j ++ )
{
generation = GC.GetGeneration(obj);
Console.WriteLine(j.ToString());
Console.WriteLine( " TotalMemory:{0} " , GC.GetTotalMemory( false ));
Console.WriteLine( " MaxGeneration:{0} " , GC.MaxGeneration);
Console.WriteLine( " Value:{0},String:{1} " , obj.Value, obj.String.Length);
Console.WriteLine( " Generation:{0} " , generation);
Console.WriteLine();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.Read();
}
class TestObject
{
public int Value = 0 ;
public string String = " 0 " ;
public TestObject()
{
for ( int j = 0 ; j < 100 ; j ++ )
{
Value ++ ;
String += j.ToString();
}
}
}
}
}
GC回收内存从0代开始,打扫0代中所有可以清除的对象。暂时不可清除的对象移到1代中。依此类推,清除1代对象时,尚用对象则移至2代。第一次回收之后,可回收内存空间已经很小,回收效果已不明显。故平常强制垃圾回收用函数GC.Collect()不如用GC.Collect(0)。
{
private function GC(){};
public static function Collect(): void
{
try {
new LocalConnection .connect( " GC1 " );
new LocalConnection .connect( " GC2 " );
} catch (e: * ){}
}
}
{
TestObject obj = new TestObject();
int generation = 0 ;
generation = GC.GetGeneration(obj);
Console.WriteLine( 0 );
Console.WriteLine( " TotalMemory:{0} " , GC.GetTotalMemory( false ));
Console.WriteLine( " MaxGeneration:{0} " , GC.MaxGeneration);
Console.WriteLine( " Value:{0},String:{1} " , obj.Value, obj.String.Length);
Console.WriteLine( " Generation:{0} " , generation);
Console.WriteLine();
try
{
new SqlConnection( " Null " ).Open();
}
catch (Exception e) { }
for ( int j = 1 ; j < 6 ; j ++ )
{
generation = GC.GetGeneration(obj);
Console.WriteLine(j.ToString());
Console.WriteLine( " TotalMemory:{0} " , GC.GetTotalMemory( false ));
Console.WriteLine( " MaxGeneration:{0} " , GC.MaxGeneration);
Console.WriteLine( " Value:{0},String:{1} " , obj.Value, obj.String.Length);
Console.WriteLine( " Generation:{0} " , generation);
Console.WriteLine();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.Read();
}
文章:浅谈C#的内存回收----关于GC、析构函数、Dispose、and Finalize
作者:sban 2007/12/1首发于博客园