XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

ASP.NET Tips: Looking at the finalization queue

So in a previous post, we talked about Understanding when to use a Finalizer in your .NET class so now lets take a look at what the Finalize queue looks like and how to tell if things are bad.

The command we use is !finalizequeue in sos:

 0:010> !finalizequeue
SyncBlock to be cleaned up: 86
----------------------------------
MTA interfaces to be released: 0
Total STA interfaces to be released: 0
----------------------------------
------------------------------
Heap 0
generation 0 has 6 finalizable objects (0x17a4e13c->0x17a4e154)
generation 1 has 0 finalizable objects (0x17a4e13c->0x17a4e13c)
generation 2 has 77 finalizable objects (0x17a4e008->0x17a4e13c)
Ready for finalization 0 objects (0x17a4e154->0x17a4e154) - Freachable queue
------------------------------
Heap 1
generation 0 has 0 finalizable objects (0x17ad591c->0x17ad591c)
generation 1 has 9 finalizable objects (0x17ad58f8->0x17ad591c)
generation 2 has 100 finalizable objects (0x17ad5768->0x17ad58f8)
Ready for finalization 2 objects (0x17ad591c->0x17ad5924) - Freachable queue  
------------------------------Ready for finalization… this show how many of the objects will need to run on the finalize thread currently. ------------------------------ Heap 2 generation 0 has 2 finalizable objects (0x17a70920->0x17a70928) generation 1 has 18 finalizable objects (0x17a708d8->0x17a70920) generation 2 has 306 finalizable objects (0x17a70410->0x17a708d8) Ready for finalization 11 objects (0x17a70928->0x17a70954) - Freachable queue ------------------------------ Heap 3 generation 0 has 0 finalizable objects (0x002295f4->0x002295f4) generation 1 has 6 finalizable objects (0x002295dc->0x002295f4) generation 2 has 155 finalizable objects (0x00229370->0x002295dc) Ready for finalization 11 objects (0x002295f4->0x00229620) - Freachable queue All Finalizable Objects Statistics: MT Count TotalSize Class Name ... 0x166c9d44 5 440 System.Data.OleDb.OleDbConnection 0x163fa3e4 12 912 System.Data.DataSet 0x79b6e18c 28 1,792 System.Threading.Thread 0x163fbad0 13 2,912 System.Data.DataTable 0x163fcdd0 88 9,240 System.Data.DataColumn Total 707 objects, Total size: 34,040

From this output we can see all of the objects that have been created that have a finalize method. 
If you call dispose on the object and it ends up calling GC.SuppressFinalize, then it will still be in this list,
but will be able to be collected like an object that does not need to be finalized. 
So other then those objects, these are all of the objects that will need to be finalized. 
After each heap you will see a line about Ready for finalization… this show how many of the objects will need to run on the finalize thread currently. 
If you use the sos that comes with the debugger package (works only with 1.x version)
you can add the -detail switch and see the Freachable Queue which is all of the objects that are ready to be finalized (GC.SuppressFinalize wasn't called but the object is ready to be cleaned up) as seen here:

 Freachable Queue Statistics:
        MT      Count     TotalSize Class Name
0x79b7d004          2            32 System.Threading.ManualResetEvent
0x16c72758          3            36 System.Data.OleDb.PropertyIDSetWrapper
0x79b89c4c          1            52 System.IO.StreamWriter
0x79ba8544          2            56 System.Security.Principal.WindowsIdentity
0x79b7df2c          2            56 Microsoft.Win32.RegistryKey
0x16c71ce8          1            56 System.Data.OleDb.DBPropSet
0x16c71434          2           128 System.Data.OleDb.DBBindings
0x79b7e8b0         10           160 System.WeakReference
0x166cbe18          2           256 System.Data.OleDb.OleDbDataReader
0x163fcdd0          3           396 System.Data.DataColumn
Total 28 objects, Total size: 1,228

When you see the !finalizequeue list showing thousands of objects, that is usually a sign that too many objects have a finalizer. 
The best way to troubleshoot this is to look for objects that are not part of the framework and then make sure that they follow the rules we discussed earlier

There are other things that can go wrong with the finalizer but we will discuss them later.  Some are already discussed on Tess’s blog.

A useful link about writing faster code is here.  This also talks about calling GC.SuppressFinalize.  There are also some very useful information in the following posts:

kick it on DotNetKicks.com 

注释

  • DotNetKicks.com
    2008年4月28日

    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Useful IIS/ASP.NET Information provided by Microsoft Support Teams
    2008年4月28日

    Want to see what objects you have added a finalizer too, follow the information here .

  • Nariman
    2009年12月1日

    Hi there; I think it's a mistake to suggest that GC.SuppressFinalize will 'remove' the object from the !finalizequeue in SOS - that you still see Systme.Data.DataTables (which call GC.SuppressFinalize) in their constructors is perfect evidence of this. Calling SuppressFinalize will prevent the promotion to FReachable and allow the object to be reclaimed on the first GC pass (just as non-finalizable objects would) but it will still list under this output. As such, this command in and of itself isn't very useful: a command in the *public* SOS that works for 2.x assemblies that shows FReachable or the Finalization Queue (where the bit in the header for finalization-requires is still set to ON) would help.

    [1]-

    http://www.devnewsgroups.net/dotnetframework/t19821-finalize-queue-windbg-sos.aspx

ASP.NET Debugging2009年12月1日

Excellent point Nariman.  I am updating the post to reflect that difference.

 


GC.SuppressFinalize(Object) Method

Definition

Requests that the common language runtime not call the finalizer for the specified object.

C#
public static void SuppressFinalize (object obj);

Parameters

obj
Object

The object whose finalizer must not be executed.

Exceptions

obj is null.

Examples

The following example demonstrates how to use the SuppressFinalize method in a resource class to prevent a redundant garbage collection from being called. The example uses the dispose pattern to free both managed resources (that is, objects that implement IDisposable) and unmanaged resources.

C#
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

public class ConsoleMonitor : IDisposable
{
   const int STD_INPUT_HANDLE = -10;
   const int STD_OUTPUT_HANDLE = -11;
   const int STD_ERROR_HANDLE = -12;

   [DllImport("kernel32.dll", SetLastError = true)]
   static extern IntPtr GetStdHandle(int nStdHandle);

   [DllImport("kernel32.dll", SetLastError = true)]
   static extern bool WriteConsole(IntPtr hConsoleOutput, string lpBuffer,
          uint nNumberOfCharsToWrite, out uint lpNumberOfCharsWritten,
          IntPtr lpReserved);

   [DllImport("kernel32.dll", SetLastError = true)]
   static extern bool CloseHandle(IntPtr handle);

   private bool disposed = false;
   private IntPtr handle;
   private Component component;

   public ConsoleMonitor()
   {
      handle = GetStdHandle(STD_OUTPUT_HANDLE);
      if (handle == IntPtr.Zero)
         throw new InvalidOperationException("A console handle is not available.");

      component = new Component();

      string output = "The ConsoleMonitor class constructor.\n";
      uint written = 0;
      WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);
   }

   // The finalizer represents Object.Finalize override.
   ~ConsoleMonitor()
   {
      if (handle != IntPtr.Zero) {
         string output = "The ConsoleMonitor finalizer.\n";
         uint written = 0;
         WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);
      }
      else {
         Console.Error.WriteLine("Object finalization.");
      }
      Dispose(disposing: false);
   }

   public void Write()
   {
      string output = "The Write method.\n";
      uint written = 0;
      WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);
   }

   public void Dispose()
   {
      string output = "The Dispose method.\n";
      uint written = 0;
      WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);

      Dispose(disposing: true);
      GC.SuppressFinalize(this);
   }

   private void Dispose(bool disposing)
   {
      string output = String.Format("The Dispose({0}) method.\n", disposing);
      uint written = 0;
      WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);

      // Execute if resources have not already been disposed.
      if (! disposed) {
         // If the call is from Dispose, free managed resources.
         if (disposing) {
            Console.Error.WriteLine("Disposing of managed resources.");
            if (component != null)
               component.Dispose();
         }
         // Free unmanaged resources.
         output = "Disposing of unmanaged resources.";
         WriteConsole(handle, output, (uint) output.Length, out written, IntPtr.Zero);

         if (handle != IntPtr.Zero) {
            if (! CloseHandle(handle))
               Console.Error.WriteLine("Handle cannot be closed.");
         }
      }
      disposed = true;
   }
}

public class Example
{
   public static void Main()
   {
      Console.WriteLine("ConsoleMonitor instance....");
      ConsoleMonitor monitor = new ConsoleMonitor();
      monitor.Write();
      monitor.Dispose();
   }
}
// If the monitor.Dispose method is not called, the example displays the following output:
//       ConsoleMonitor instance....
//       The ConsoleMonitor class constructor.
//       The Write method.
//       The ConsoleMonitor finalizer.
//       The Dispose(False) method.
//       Disposing of unmanaged resources.
//
// If the monitor.Dispose method is called, the example displays the following output:
//       ConsoleMonitor instance....
//       The ConsoleMonitor class constructor.
//       The Write method.
//       The Dispose method.
//       The Dispose(True) method.
//       Disposing of managed resources.
//       Disposing of unmanaged resources.

Remarks

This method sets a bit in the object header of obj, which the runtime checks when calling finalizers. A finalizer, which is represented by the Object.Finalize method, is used to release unmanaged resources before an object is garbage-collected. If obj does not have a finalizer or the GC has already signaled the finalizer thread to run the finalizer, the call to the SuppressFinalize method has no effect.

Objects that implement the IDisposable interface can call this method from the object's IDisposable.Dispose implementation to prevent the garbage collector from calling Object.Finalize on an object that does not require it. Typically, this is done to prevent the finalizer from releasing unmanaged resources that have already been freed by the IDisposable.Dispose implementation.

Applies to

ProductVersions
.NET Core 1.0, Core 1.1, Core 2.0, Core 2.1, Core 2.2, Core 3.0, Core 3.1, 5, 6, 7
.NET Framework 1.1, 2.0, 3.0, 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1
.NET Standard 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 2.0, 2.1
UWP 10.0
Xamarin.iOS 10.8
Xamarin.Mac 3.0

See also


从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制

 

如果你经常看开源项目的源码,你会发现很多Dispose方法中都有这么一句代码: GC.SuppressFinalize(this); ,看过一两次可能无所谓,看多了就来了兴趣,这篇就跟大家聊一聊。

一:背景

1. 在哪发现的

相信现在Mysql在.Net领域中铺的面越来越广了,C#对接MySql的MySql.Data类库的代码大家可以研究研究,几乎所有操作数据库的几大对象:MySqlConnection,MySqlCommand,MySqlDataReader以及内部的Driver都存在 GC.SuppressFinalize(this)代码。


public sealed class MySqlConnection : DbConnection, ICloneable
{
    public new void Dispose()
    {
	    Dispose(disposing: true);
	    GC.SuppressFinalize(this);
    }
}

public sealed class MySqlCommand : DbCommand, IDisposable, ICloneable
{
    public new void Dispose()
    {
	    Dispose(disposing: true);
	    GC.SuppressFinalize(this);
    }
}

2. GC.SuppressFinalize 场景在哪里

先看一下官方对这个方法的解释,如下所示:

        //
        // Summary:
        //     Requests that the common language runtime not call the finalizer for the specified
        //     object.
        //
        // Parameters:
        //   obj:
        //     The object whose finalizer must not be executed.
        //
        // Exceptions:
        //   T:System.ArgumentNullException:
        //     obj is null.
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SecuritySafeCritical]
        public static void SuppressFinalize(object obj);

意思就是说: 请求 CLR 不要调用指定对象的终结器,如果你对终结器的前置基础知识不足,那这句话肯定不是很明白,既然都执行了Dispose,说明非托管资源都被释放了,怎么还压制CLR不要调用Finalize呢?删掉和不删掉这句代码有没有什么严重的后果,GC类的方法谁也不敢动哈。。。 为了彻底讲清楚,有必要说一下Finalize整个原理。

二:资源管理

我们都知道C#是一门托管语言,它的好处就是不需要程序员去关心内存的分配和释放,由CLR统一管理,这样编程门槛大大降低,天下攘攘皆为利来,速成系的程序员就越来越多~

1. 对托管资源和非托管资源理解

<1> 托管资源

这个很好理解,你在C#中使用的值类型,引用类型都是统一受CLR分配和GC清理。

<2> 非托管资源

在实际业务开发中,我们的代码不可能不与外界资源打交道,比如说文件系统,外部网站,数据库等等,就拿写入文件的StreamWriter举例,如下代码:

        public static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("xxx.txt");
            sw.WriteLine("....");
        }

为什么能够写入文件? 那是因为我们的代码是请求windows底层的Win32 Api帮忙写入的,这就有意思了,因为这个场景有第三者介入,sw是引用类型受CLR管理,win32 api属于外部资源和.Net一点关系都没有,如果你在用完sw之后没有调用close方法的话,当某个时候GC回收了托管堆上的sw后,这给被打开的win32 api文件句柄再也没有人可以释放了,资源就泄露了,如果没看懂,我画张图:

三:头疼的非托管资源解决方案

1. 使用析构函数

很多时候程序员就是在使用完类之后因为种种原因忘记了手动执行Close方法造成了资源泄露,那有没有一种机制可以在GC回收堆对象的时候回调我的一个自定义方法呢?如果能实现就🐮👃了,这样我就可以在自定义方法中做全局的控制。

其实这个自定义方法就是析构函数,接下来我把上面的 StreamWriter 改造下,将 Close() 方法放置在析构函数中,先看一下代码:


    public class Program
    {
        public static void Main(string[] args)
        {
            MyStreamWriter sw = new MyStreamWriter("xxx.txt");
            sw.WriteLine("....");

            GC.Collect();
            Console.ReadLine();
        }
    }

    public class MyStreamWriter : StreamWriter
    {
        public MyStreamWriter(string filename) : base(filename) { }

        ~MyStreamWriter()
        {
            Console.WriteLine("嘿嘿,忘记调用Close方法了吧! 我来帮你");
            base.Dispose(false);
            Console.WriteLine("非托管资源已经帮你释放啦,不要操心了哈");
        }
    }

--------- output -----------

嘿嘿,忘记调用Close方法了吧! 我来帮你
非托管资源已经帮你释放啦,不要操心了哈

四: 析构函数被执行的底层原理分析

让GC来通知我的回调方法这本身就很🐮👃,但仔细想想,在垃圾回收时,CLR不是将所有线程都挂起了吗?怎么还有活动的线程,而且这个线程是来自哪里? 线程池吗? 好,先从理论跟和大家分析一下,析构函数在CLR层面称为Finalize方法,为了方便后面通过windbg去验证,这里统一都叫Finalize方法,提前告知。

1. 原理步骤

<1> CLR在启动时会构建一个“Finalize全局数组”和“待处理Finalize数组” ,所有定义Finalize方法的类,它的引用地址全部额外再灌到“Finalize全局数组”中。

<2> CLR启动一个专门的“Finalize线程”让其全权监视“待处理Finalize数组”。

<3> GC在开启清理前标记对象引用时,如发现某一个对象只有一个在Finalize数组中的引用,说明此对象是垃圾了,CLR将该对象地址转移到另外一个 “待处理Finalize” 数组中。

<4> 由于该对象还存在引用,所以GC放了一马,然后“Finalize线程”监视到了 “待处理Finalize数组” 新增的对象,取出该对象并执行该对象的Finalize方法。

<5> 由于是破坏性取出,此时该对象再无任何引用,下次GC启动时就会清理出去。

看文字有点绕,我画一张图帮大家理解下。

2. windbg验证

<1> 修改Main代码如下,抓一下dump文件看看 MyStreamWriter是否在Finalize全局数组中。


        public static void Main(string[] args)
        {
            MyStreamWriter sw = new MyStreamWriter("xxx.txt");
            sw.WriteLine("....");

            Console.ReadLine();
        }

``` C#

0:000> !FinalizeQueue
SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 13 finalizable objects (0000018c2a9b7a80->0000018c2a9b7ae8)
generation 1 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)
generation 2 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)
Ready for finalization 0 objects (0000018c2a9b7ae8->0000018c2a9b7ae8)
Statistics for all finalizable objects (including all objects ready for finalization):
              MT    Count    TotalSize Class Name
00007ff8e7afb2a8        1           32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle
00007ff8e7a94078        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle
00007ff8e7a843b0        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
00007ff8e7a84320        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00007ff8e7b001b8        1           40 System.Runtime.InteropServices.SafeHeapHandleCache
00007ff8e7ad6df0        1           40 System.Runtime.InteropServices.SafeHeapHandle
00007ff8e7b133d0        2           64 Microsoft.Win32.SafeHandles.SafeRegistryHandle
00007ff8e7a995d0        2           64 Microsoft.Win32.SafeHandles.SafeFileHandle
00007ff8e7a93b48        1           64 System.Threading.ReaderWriterLock
00007ff8e7b14d38        1          104 System.IO.FileStream
00007ff889d45b18        1          112 ConsoleApp2.MyStreamWriter
Total 13 objects

很惊喜的看到 MyStreamWriter 就在其中,符合图中所示。

<2> 查看是否有专门的 “Finalize线程” ,可以通过 !threads 命令查看。


0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1  bf4 0000018c2a990f00    2a020 Preemptive  0000018C2C429168:0000018C2C429FD0 0000018c2a965220 1     MTA 
   6    2 44f4 0000018c2a9b9450    2b220 Preemptive  0000000000000000:0000000000000000 0000018c2a965220 0     MTA (Finalizer) 


看到没,线程2标记了 MTA (Finalizer), 说明果然有执行Finalizer方法的专有线程。😁😁😁

<3> 由于水平有限,不知道怎么去看 “待处理Finalize数组”,所以只能验证等GC回收之后,看下 “Finalize全局数组”中是否还存在MyStreamWriter即可。


        public static void Main(string[] args)
        {
            MyStreamWriter sw = new MyStreamWriter("xxx.txt");
            sw.WriteLine("....");
            GC.Collect();
            Console.ReadLine();
        }

------- output ---------

嘿嘿,忘记调用Close方法了吧! 我来帮你
非托管资源已经帮你释放啦,不要操心了哈


0:000> !FinalizeQueue
SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 5 finalizable objects (0000021e8051a798->0000021e8051a7c0)
generation 1 has 5 finalizable objects (0000021e8051a770->0000021e8051a798)
generation 2 has 0 finalizable objects (0000021e8051a770->0000021e8051a770)
Ready for finalization 0 objects (0000021e8051a7c0->0000021e8051a7c0)
Statistics for all finalizable objects (including all objects ready for finalization):
              MT    Count    TotalSize Class Name
00007ff8e7afb2a8        1           32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle
00007ff8e7a94078        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle
00007ff8e7a843b0        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
00007ff8e7a84320        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00007ff8e7b001b8        1           40 System.Runtime.InteropServices.SafeHeapHandleCache
00007ff8e7ad6df0        1           40 System.Runtime.InteropServices.SafeHeapHandle
00007ff8e7a995d0        2           64 Microsoft.Win32.SafeHandles.SafeFileHandle
00007ff8e7a93b48        1           64 System.Threading.ReaderWriterLock
00007ff8e7a96a10        1           96 System.Threading.Thread
Total 10 objects

可以看到这时候 “全局数组” 没有引用了,再看一下托管堆是否还存在 MyStreamWriter以及线程栈中是否还有对象引用地址。


0:000> !dumpheap 
         Address               MT     Size
00007ff889d25b00        1          112 ConsoleApp2.MyStreamWriter

Total 423 objects

0:000> !clrstack -l
OS Thread Id: 0x1b00 (0)
        Child SP               IP Call Site
0000007ecdffe9e0 00007ff8e88c20cc System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)
    LOCALS:
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>
0000007ecdffea70 00007ff8e88c1fd5 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
    LOCALS:
        <no data>
        <no data>
0000007ecdffead0 00007ff8e80770f4 System.IO.StreamReader.ReadBuffer()
    LOCALS:
        <no data>
        <no data>
0000007ecdffeb20 00007ff8e8077593 System.IO.StreamReader.ReadLine()
    LOCALS:
        <no data>
        <no data>
        <no data>
        <no data>
0000007ecdffeb80 00007ff8e8a68b0d System.IO.TextReader+SyncTextReader.ReadLine()
0000007ecdffebe0 00007ff8e8860d98 System.Console.ReadLine()
0000007ecdffec10 00007ff889e30959 ConsoleApp2.Program.Main(System.String[])
0000007ecdffeea8 00007ff8e9396c93 [GCFrame: 0000007ecdffeea8] 

可以看到MyStreamWriter还是存在于托管堆,但是线程栈已再无它的引用地址,就这样告别了全世界,下次GC启动就要被彻底运走了。

五:回头再看 SuppressFinalize

如果你看懂了上面 Finalize 原理,再来看 SuppressFinalize的解释:‘请求 CLR 不要调用指定对象的终结器’。

就是说当你手动调用Dispose或者Close方法释放了非托管资源后,通过此方法强制告诉CLR不要再触发我的析构函数了,否则再执行析构函数相当于又做了一次清理非托管资源的操作,造成未知风险。

好了,本篇就说这么多,希望你对有帮助。

posted on 2023-02-06 11:01  不及格的程序员-八神  阅读(3)  评论(0编辑  收藏  举报