[C#]这里objA引用的对象并没有被回收,因为这个对象还有另一个引用,ObjB。 对象在没有任何引用后就有条件被回收了。当GC回收时,它会做以下几步: 确定对象没有任何引用。 检查对象是否在Finalizer表上有记录。 如果在Finalizer表上有记录,那么将记录移到另外的一张表上,在这里我们叫它Finalizer2。 如果不在Finalizer2表上有记录,那么释放内存。 在 Finalizer2表上的对象的Finalizer会在另外一个low priority的线程上执行后从表上删除。当对象被创建时GC会检查对象是否有Finalizer,如果有就会在Finalizer表中添加纪录。我们这里所说的记录其实就是指针。如果仔细看这几个步骤,我们就会发现有Finalizer的对象第一次不会被回收,也就是,有Finalizer的对象要一次以上的Collect操作才会被回收,这样就要慢一步,所以作者推荐除非是绝对需要不要创建Finalizer。为了证明GC确实这么工作而不是作者胡说,我们将在对象的复活一章中给出一个示例,眼见为实,耳听为虚嘛!^_^ GC为了提高回收的效率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Generation 1中回收,如果还不够就在Generation 2中回收,以此类推。Generation也有个最大限制,根据Framework版本而定,可以用GC.MaxGeneration获得。在回收了内存之后GC会重新排整内存,让数据间没有空格,这样是因为CLR顺序分配内存,所以内存之间不能有空着的内存。现在我们知道每次回收时都会浪费一定的CPU 时间,这就是我说的一般不要手动GC.Collect的原因(除非你也像我一样,写一些有关GC的示例!^_^)。
object objA = new object();
object objB = objA;
objA = null;
// 强迫回收。
GC.Collect();
objB.ToString();
[Visual Basic]
Dim objA As New Object()
Dim objB As Object = objA
objA = Nothing
' 强迫回收。
GC.Collect()
objB.ToString()
[C#]注意以上代码要是改用C++写的话会发生内存泄漏,因为我们没有用delete操作符手动清理内存,但是在托管代码中却不会发生内存泄漏,因为GC会自动检测没有引用了的对象并回收。这里作者推荐你只在实现IDisposable接口时配合使用Finalizer,在其他的情况下不要使用(可能会有特殊情况)。在非托管资源的释放一章我们会更好的了解IDisposable接口,现在让我们来做耶稣吧!
public class CountObject {
public static int Count = 0;
public CountObject() {
Count++;
}
~CountObject() {
Count--;
}
}
static void Main() {
CountObject obj;
for (int i = 0; i < 5; i++) {
obj = null; // 这一步多余,这么写只是为了更清晰些!
obj = new CountObject();
}
// Count不会是1,因为Finalizer不会马上被触发,要等到有一次回收操作后才会被触发。
Console.WriteLine(CountObject.Count);
Console.ReadLine();
}
[C#]你可能会问:"既然这个对象能复活,那么这个对象在程序结束后会被回收吗?"。会,"为什么?"。让我们按照GC的工作方式走一遍你就明白是怎么回事了。 1、执行Collect。检查引用。没问题,对象已经没有引用了。 2、创建新实例时已经在Finalizer表上作了纪录,所以我们检查到了对象有Finalizer。 3、因为查到了Finalizer,所以将记录移到Finalizer2表上。 4、在Finalizer2表上有记录,所以不释放内存。 5、Collect执行完毕。这时我们用了GC.WaitForPendingFinalizers,所以我们将等待所有Finalizer2表上的Finalizers的执行。 6、Finalizer执行后我们的Instance就又引用了我们的对象。(复活了) 7、再一次去除所有的引用。 8、执行Collect。检查引用。没问题。 9、由于上次已经将记录从Finalizer表删除,所以这次没有查到对象有Finalizer。 10、在Finalizer2表上也不存在,所以对象的内存被释放了。 现在你明白原因了,让我来告诉你"复活"的用处。嗯,这个……好吧,我不知道。其实,复活没有什么用处,而且这样做也非常的危险。看来这只能说是GC机制的漏洞(请参看GC.ReRegisterForFinalize再动脑筋想一下就知道为什么可以说是漏洞了)。作者建议大家忘掉有什么复活,避免这类的使用。可能你会问:"那你干吗还要对我们说这些?"我说这些为的是让大家更好的了解GC的工作机制!^_^
public class Resurrection {
public int Data;
public Resurrection(int data) {
this.Data = data;
}
~Resurrection() {
Main.Instance = this;
}
}
public class Main {
public static Resurrection Instance;
public static void Main() {
Instance = new Resurrection(1);
Instance = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// 看到了吗,在这里“复活”了。
Console.WriteLine(Instance.Data);
Instance = null;
GC.Collect();
Console.ReadLine();
}
}
[Visual Basic]
Public Class Resurrection
Public Data As Integer
Public Sub New(ByVal data As Integer)
Me.Data = data
End Sub
Protected Overrides Sub Finalize()
Main.Instance = Me
MyBase.Finalize()
End Sub
End Class
Public Class Main
Public Shared Instance As Resurrection
Sub Main()
Instance = New Resurrection(1)
Instance = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
' 看到了吗,在这里“复活”了。
Console.WriteLine(Instance.Data)
Instance = Nothing
GC.Collect()
Console.ReadLine()
End Sub
End Class
[C#]为什么要这样设计呢?让我在后面解说一下。现在我们讲讲实现这个Dispose方法的几个准则: 它不能扔出任何错误,重复的调用也不能扔出错误。也就是说,如果我已经调用了一个对象的Dispose,当我第二次调用Dispose的时候程序不应该出错,简单地说程序在第二次调用Dispose时不会做任何事。这些可以通过一个flag或多重if判断实现。 一个对象的Dispose要做到释放这个对象的所有资源。拿一个继承类为例,继承类中用到了非托管资源所以它实现了IDisposable接口,如果继承类的基类也用到了非托管资源那么基类也得被释放,基类的资源如何在继承类中释放呢?当然是通过一个virtual/Overridable方法了,这样我们能保证每个Dispose都被调用到。这就是为什么我们的设计有一个virtual/Overridable的Dispose方法。注意我们首先要释放继承类的资源然后再释放基类的资源。 因为非托管资源一定要被保障正确释放所以我们要定义一个Finalizer来避免程序员忘了调用Dispose的情况。上面的设计就采用了这种形式。如果我们手动调用Dispose方法就没有必要再保留 Finalizer了,所以在Dispose中我们用了GC.SupressFinalize将对象从Finalizer表去掉,这样再回收时速度会更快。 那么那个disposing和"托管类"是怎么回事呢?是这样:在"托管类"中写所有你想在调用Dispose时让其处于可释放状态的托管代码。还记得我们说过我们不知道托管代码是什么时候释放的吗?在这里我们只是去掉成员对象的引用让它处于可被回收状态,并不是直接释放内存。在"托管类"中这里我们也要写上所有实现了IDisposable的成员对象,因为他们也有Dispose,所以也需要在对象的Dispose中调用他们的Dispose,这样才能保证第二个准则。disposing是为了区分Dispose的调用方法,如果我们手动调用,那么为了第二个准则"托管类"部分当然得执行,但如果是Finalizer调用的Dispose,这时候对象已经没有任何引用,也就是说对象的成员自然也就不存在了(无引用),也就没有必要执行"托管类"部分了,因为他们已经处于可被回收状态了。好了,这就是IDisposable接口的全部了。现在让我们来回想一下,以前我们可能认为有了Dispose内存就会马上被释放,这是错误的。只有非托管内存才会被马上释放,托管内存的释放由GC管理,我们不用管。
public class Base : IDisposable {
public void Dispose() {
this.Dispose(true);
GC.SupressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
// 托管类
}
// 非托管资源释放
}
~Base() {
this.Dispose(false);
}
}
public class Derive : Base {
protected override void Dispose(bool disposing) {
if (disposing) {
// 托管类
}
// 非托管资源释放
base.Dispose(disposing);
}
}
[Visual Basic]
Public Class Base
Implements IDisposable
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overloads Overridable Sub Dispose(ByVal disposing As Boolean)
If disposing Then
' 托管类
End If
' 非托管资源释放
End Sub
Protected Overrides Sub Finalize()
Me.Dispose(False)
MyBase.Finalize()
End Sub
End Class
Public Class Derive
Inherits Base
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
' 托管类
End If
' 非托管资源释放
MyBase.Dispose(disposing)
End Sub
End Class
[C#]这里我们的Fat其实并不是很胖,但是可以体现示例的本意:如何使用弱引用。那如果Fat有Finalizer呢,会怎样?如果Fat有Finalizer那么我们可能会用到WeakReference的另一个构造函数,当中有一参数叫做TrackResurrection,如果是True,只要Fat的内存没被释放我们就可以用它,也就是说Fat的Finalizer执行后我们还是可以恢复Fat(相当于第一次回收操作后还可恢复Fat);如果TrackResurrection是False,那么第一次回收操作后就不能恢复Fat对象了。
public class Fat {
public int Data;
public Fat(int data) {
this.Data = data;
}
}
public class Main {
public static void Main() {
Fat oFat = new Fat(1);
WeakReference oFatRef = new WeakReference(oFat);
// 从这里开始,Fat对象可以被回收了。
oFat = null;
if (oFatRef.IsAlive) {
Console.WriteLine(((Fat) oFatRef.Target).Data); // 1
}
// 强制回收。
GC.Collect();
Console.WriteLine(oFatRef.IsAlive); // False
Console.ReadLine();
}
}
[Visual Basic]
Public Class Fat
Public Data As Integer
Public Sub New(ByVal data As Integer)
Me.Data = data
End Sub
End Class
Public Module Main
Sub Main()
Dim oFat As New Fat(1)
Dim oFatRef As New WeakReference(oFat)
' 从这里开始,Fat对象可以被回收了。
oFat = Nothing
If oFatRef.IsAlive Then
Console.WriteLine(DirectCast(oFatRef.Target, Fat).Data) ' 1
End If
' 强制回收。
GC.Collect()
Console.WriteLine(oFatRef.IsAlive) ' False
Console.ReadLine()
End Sub
End Module
欢迎访问最专业的网吧论坛,无盘论坛,网吧经营,网咖管理,网吧专业论坛
https://bbs.txwb.com
关注天下网吧微信/下载天下网吧APP/天下网吧小程序,一起来超精彩
|
本文来源:vczx 作者:佚名