将我刚才100分的垃圾回收讨论帖的问题具体话,再讨论下。。(最后38分,不要只看分,进来讨论学习一下)

v41dugu 2008-11-10 02:20:05
class B : IDisposable
{
sqlConnection sqlCon = new .........
}

B包含了sql连接,也就是B持有非托管资源。
B正确实现了dispose和析构函数(finalize方法)



class A : IDisposable
{
B b = new B();
......
~B()
{
Dispose(false);
}
private virtual void Dispose(bool disposing)
{
.........//省略其他代码
if(disposing) //表示手动调用
{
b.dispose();
}
//还是b.dispose()应该写这里
}

}

如果是对于手动调用A的dispose方法, 那么B的dispose随便放哪里,都没有区别。
所以问题就在finalize的时候(垃圾回收的时候),两种情况:
1. 像我上面代码那样,b.dispose写在if语句里面,也就是如果是垃圾回收自动调用的,就不会运行b.dispose方法。 要注意一点,由于b是A的成员,所以在外部不引用b的情况下,A和B应该是同时出现不可到达的情况,也就是他们的finalize方法会在同一次垃圾回收的时候调用,只是先后顺序不确定(假设A和B的对象都处于统一代龄)。 所以在这种情况下,不管A和B的finalize哪个先调用,这个sql的链接都会在这次垃圾回收的时候释放,而下次垃圾回收,A和B的全部内存才会被释放掉。也就是分别调用两个类的finalize是一定的。

2. b.dispose写在if语句外面,如果是手动调用就不必说了,直接就把sql链接释放了,第1次垃圾回收的时候再干掉占用的内存。
但是如果是忘了调用A的dispose,垃圾回收的时候调用A的finalize,那么对于b,有两种情况:
① b自己的finalize已经调用。这种情况下,b的dispose还是会运行,只是直接就返回了,没有任何其他意义。(其实我多次测试的情况都是b的finalize自己先调用了,也就是内部成员的finalize先调用了)。其实这种情况让我联想到另一种情况,就是A直接包含的是sqlCon,而没有B。 那么在A的finalize被自动调用的时候,其实sqlCon的finalize大多数情况下也已经自动调用了,其实那个sqlCon.Dispose(),也没起什么作用 。
所以这里我就提出我的疑问,微软所说的在 if(disposing)中用包含了非托管资源的类(这里的sqlCon)的Dispose方法来释放资源,真正起作用的又有多少次呢? 因为运行了几十次程序,只要是一个类包含了另一个类,两个都实现了finalize,那么内部对象的finalize总是先执行。 当然也许你要说万一内部内的后执行呢?当然这种情况的话,内部对象的dispose就起作用了。 给我的感觉就是,如果是垃圾回收来处理A,那么b.dispose基本上没派上什么用场。 而且A中包含了N个像b这种实现了finalize的对象,那么其实如果你在垃圾回收对 A 工作之前,对所有的B类型的对象没有显示的释放,那么当GC对A开始清理的时候,消耗会相当大。因为所有的内部对象都会先调用自己的finalize。(当然这只是我自己的测试结果,微软也说finalize的调用顺序是不一定的。 但是我测试多次都是这样,相信只有出现特殊点的情况,才会遇到微软说的不一定的调用顺序)。

② b的finalize还没有调用: 这种情况当然b的dispose就很有用了。 不过就算我们不调用b的dispose,这次垃圾回收还是会调用b的finalize. 感觉没什么差别。 当然你也许会说调用finalize要比我们手动调用dispose消耗更大。 这个我也承认。

总之就是,这个b.dispose到底是放 if 语句里面 还是外面 , 确实需要考虑一下。 上面就是我的见解, 请大家批斗!!


...全文
113 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
gomoku 2008-11-11
  • 打赏
  • 举报
回复
我看了你在9楼的回复。你的测试的正确的,我的描述有错误。不过你要注意执行Finalizer和对象置零是两回事。

在每一回合的垃圾回收中,为了让Finzalizer不会引用到已经销毁的对象,CLR在标志所有可回收对象后,把带有Finalizer的对象被放到一个队列中,并先执行该队列中对象的Finzlizer,然后才开始销毁对象。如果在执行Finalizer中出现了对其他可回收对象的引用,那么,该其他对象就被复活。

由于Finalizer并不给引用置零,它也不直接销毁对象。因此回收垃圾并不会带来你担心的NullReference;而程序显式的置零带来的NullRefrence则属于编程逻辑错误。





using System;
class MyClass
{
Base b = new Base();
~MyClass()
{
Console.WriteLine("MyClasss的finalize函数");

Program.sBase = b; //<--复活Base
GC.ReRegisterForFinalize(b);
}
}

class Base
{
~Base()
{
Console.WriteLine("Base的finalize函数");
}

}
class Program
{
public static Base sBase;

static void Main(string[] args)
{
MyClass m = new MyClass();
m = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.Read();

Program.sBase = null;
GC.Collect();
Console.Read();
}
}




v41dugu 2008-11-10
  • 打赏
  • 举报
回复
其实还有种情况:
A中包含sqlConnection对象sqlCon.

但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。 为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生
///////////////////////////////////////////////////////////////////////
关于这个问题,我查阅到MSND的网络版,上面有段伪代码:
http://msdn.microsoft.com/zh-cn/library/ms244737.aspx


我提取一部分出来 (看起来像C#,但它说的是伪代码)

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
if (managedResource != null)
{
managedResource.Dispose();
managedResource = null;
}
}
// free native resources if there are any.
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource = IntPtr.Zero;
}
}


它这里居然是做了判断 if (managedResource != null)
还有 if (nativeResource != IntPtr.Zero)
对于托管和非托管都做了判断。 但是我没找到c#代码,不知道是不是也是这样
但是它的这个写法 ,至少肯定了我所说的, 这个IntPtr的Finalize可能已经先调用了,甚至连内存已经被回收了(我觉得出现内存都已经被回收了,就是因为代龄的原因)。 所以每个对象都先判断其引用是不是null才是真的正确的 。

我一直困惑的就是为什么那么多书里都没有写出这点。 而我偶然看到这里的伪代码居然有写。 但是其他大师们却一直没注意这一点, 确实比较不解。 如果了解代龄的运作方式,要想到这个问题也不难。

请各位批评指教。。。。。。。。
v41dugu 2008-11-10
  • 打赏
  • 举报
回复
虽然我们讲的不是一回事:) 这里还是可以澄清你的一个疑问:
1、如果你不在A里面把sqlCon置零的话,SqlCon的finalizer不会先执行,因为A保留了一个到sqlCon的引用。
2、如果你自己在A里面把sqlCon置零的话,我想你不至于再故意调用sqlCon.Dispose()吧。


///////////////////////
我觉得你说得不对。 第1点里, 虽然A有sqlCon的引用,但是既然A已经在执行finalize了,说明A的引用已经为空了。 root都已经不可到达了,里面的子节点都是不可到达的(.NET框架程序设计中对finalize的解释中有提到)。

请问你写过代码去测试吗?请看我的测试代码

class Base
{
~Base()
{
Console.WriteLine("Base的finalize函数");
}

}
class MyClass
{
Base b = new Base();
~MyClass()
{
Console.WriteLine("MyClasss的finalize函数");
}
}


class Program
{

static void Main(string[] args)
{
MyClass m = new MyClass();
m = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.Read();
}
}

运行结果,确实是Base的Finalize函数先于MyClass的Finalize运行。
当然sqlCon的 我们看不到它的Finalize运行情况,但是大家都一样是重写了Finalize的,情况应该想通吧。

所以 我觉得你的观点不正确。
风骑士之怒 2008-11-10
  • 打赏
  • 举报
回复
无法确定在某个时间点执行垃圾回收
aboiljelly 2008-11-10
  • 打赏
  • 举报
回复
应该是肯定被释放但是无法预期什么时候释放掉吧
wesleyluo 2008-11-10
  • 打赏
  • 举报
回复
学习学习,讲的都有些不懂了。
gimse7en 2008-11-10
  • 打赏
  • 举报
回复
mark
gomoku 2008-11-10
  • 打赏
  • 举报
回复
其实还有种情况:
A中包含sqlConnection对象sqlCon.

但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。 为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生


虽然我们讲的不是一回事:) 这里还是可以澄清你的一个疑问:
1、如果你不在A里面把sqlCon置零的话,SqlCon的finalizer不会先执行,因为A保留了一个到sqlCon的引用。
2、如果你自己在A里面把sqlCon置零的话,我想你不至于再故意调用sqlCon.Dispose()吧。

v41dugu 2008-11-10
  • 打赏
  • 举报
回复
其实还有种情况:
A中包含sqlConnection对象sqlCon.

但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。 为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生
gomoku 2008-11-10
  • 打赏
  • 举报
回复
在你的另一个贴中我提过IComponent和IContainer是更好的管理资源的模式。

组件(IComponent)直接继承于IDisposable,并提供Disposed事件以便其他人追踪它的生命周期。其中ISite让我们知道它被那个容器所包含。
容器(IContainer)是封装和跟踪零个或更多个组件的对象。通过Components接口可以知道它所包含的组件。一个典型的IContainer在它Dispose的时候,有责任Dispose它所包含的组件。

比如如下代码是微软例子的节选,我们可以看到,这个IContainer在它Dispose的时候,Dipose了它所包含的组件。

class LibraryContainer : IContainer
{
private ArrayList m_bookList;

//...
//...

public virtual void Dispose()
{
for (int i = 0; i < m_bookList.Count; ++i)
{
IComponent curObj = (IComponent)m_bookList[i];
curObj.Dispose();
}

m_bookList.Clear();
}
}


组件一定是IDisposable,通过组件,可以有更清晰的模式来管理需要及时回收的资源,组件的生命周期可以更有预测性,更容易管理。








public interface IComponent : IDisposable
{
ISite Site { get; set; }
event EventHandler Disposed;
}

public interface IContainer : IDisposable
{
ComponentCollection Components { get; }
void Add(IComponent component);
void Add(IComponent component, string name);
void Remove(IComponent component);
}


zjybushiren88888 2008-11-10
  • 打赏
  • 举报
回复
垃圾回收机制-..不稳定性

110,546

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧