troushoo

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

  1. --/--/--(--) --:--:--|
  2. スポンサー広告

[sos]!finalizequeue:Freachable キューの状態表示

WinDbg等で使用できる.NET用のデバッガエクステンションsosのコマンド!finalizequeueについて記述します。
!finalzequeueを利用すると、ファイナライザの実装の問題を検出することが可能です。

ファイナライザとは?
ファイナライザとは、どのクラスにも指定できるオプションのメンバで、C#で記述するには~<クラス名>という構文を利用します。
例:クラス名がMyClassの場合、ファイナライザは~MyClass()となります。

class MyClass
{
    ~MyClass() //←ファイナライザ
    {
    }
}
 
なぜファイナライザが必要か?
マネージオブジェクトは使われなくなるとガーベッジコレクタにより.NETシステムから解放されます。しかしファイルハンドラやソケットといったアンマネージリソースをマネージオブジェクトが保持している場合、アンマネージリソースはガーベッジコレクタでは解放することができません。.NETシステムではアンマネージリソースは管理できないからです。
したがってガーベッジコレクタがマネージオブジェクトを解放する時に、アンマネージオブジェクトも解放するメソッドとしてファイナライザが必要になります。

ファイナライザに実装の問題があるとどうなるか?
ファイナライザに実装の問題があると、Freachable(エフ リーチャブルと発音します)キューに多くのオブジェクトが格納されてしまうことがあります(具体例は後述します)。Freachableキューは.NETのシステムが保持するキューです(詳細は後述します)。すなわちFreachableキューに多くのオブジェクトが格納されると、アプリのメモリを圧縮しパフォーマンス低下につながることがあります。

Freachableキューとは?
以下のような状態を例にとりFreachableキューを説明します。ヒープにオブジェクトA, B, ・・・Lがある状態です。図左下のファイナライゼーションキューとは、.NETシステムが保持するキューの一種で、ファイナライザを持つオブジェクトが格納されます。

この時、オブジェクトB, E, G, H, I, J(下図の赤色のオブジェクト)がガーベッジコレクタにより解放されているとします。

すると、ファイナライゼーションキューにあるオブジェクトE, I, Jはファイナライゼーションキューから取り除かれ、Freachableキューに移動させられます。そしてヒープ上にある自分自身のオブジェクトをポイントします。ポイントする理由は、ファイナライザを呼ぶ前にガーベッジコレクトされないためです。
ガーベッジコレクションの後は下記のようになります。オブジェクトB, G, Hは解放されましたが、ファイナライザをもつオブジェクトE, I, Jは解放されてません。

Freachableキューに入ったオブジェクトは、.NETランタイムのファイナライザを呼ぶためのスレッドによりファイナライザが呼ばれます。このスレッドはFreachableキューにオブジェクトがない場合はスリープしていますが、Freachableにオブジェクトが入ると、そのオブジェクトのファイナライザを呼び、Freachableにあるオブジェクトを削除します。
以下はオブジェクトE, I, Jのファイナライザが呼ばれ、それぞれのオブジェクトがFreachableキューから消された状態です。

次にガーベッジコレクタが呼ばれた際は、オブジェクトE, I, Jへの参照はなくなっているため、オブジェクトE, I, Jも解放されます。

このようにFreachableキューは利用されます。

ファイナライザに実装の問題があるサンプルアプリ
ファイナライザに実装の問題があるために、Freachableキューにオブジェクトがたまってしまうサンプルアプリを見てみます。
以下のアプリはMyClassのファイナライザがwhile(true)のため終了しません。そのためFreachableキューのオブジェクトが削除されずに残ったままになってしまいます。
MyClassはサイズの大きいメンバー変数も持っているため、ユーザーから見ると「アプリがメモリを食っている」、「アプリがメモリリークを起こしている」といった状態に見えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace finalizequeue
{
    class MyClass
    {
        int[] intarray = new int[10000000]; //サイズの大きい変数
 
        ~MyClass() //ファイナライザ
        {
          //無限に待ち続ける
            //すなわちファイナライザは終わらない
            while (true) { Thread.Sleep(1000); } 
        }
    }
 
    class Program
    {
        static public void MyFunc()
        {
            //MyClassを3つ作成
              MyClass mc1 = new MyClass();
            MyClass mc2 = new MyClass();
            MyClass mc3 = new MyClass();
        }
 
        static void Main(string[] args)
        {
            Console.ReadLine(); //ユーザーからのEnterを待つため(デモ用)
            MyFunc();
            GC.Collect(); //ガーベッジコレクションを行う
 
            while (true) { Thread.Sleep(1000); }//スレッドを終了させないためのループ(デモ用)
        }
    }
}

以下はEnterを押した前後でのPrivate Bytesの使用量を表しており、Private Bytesが急増していることがわかります。

このアプリを、ダンプ調査のみで"ファイナライザに問題がある"ということを突き止めます。

ダンプ調査
1. Enterを押した後のダンプを取得します。
(補足) 32bitの.NETアプリのダンプを取るには32bitのタスクマネージャーでダンプを取る必要があります。詳細は、以前のブログ: .NETの64bit OS上の32bitプロセスのダンプの取得方法をご参照ください。


2. WinDbgでダンプを開きます。
(補足).NETの32bitアプリのダンプは、32ビットのWinDbgで開く必要があります。

3. シンボル、sosを読み込んで!finalizequeueコマンドを実行します。
0:000> .symfix c:\sym
0:000> .reload
...........................
0:000> .loadby sos clr
0:000> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 0 finalizable objects (007499b0->007499b0)
generation 1 has 0 finalizable objects (007499b0->007499b0)
generation 2 has 6 finalizable objects (00749998->007499b0)
Ready for finalization 2 objects (007499b0->007499b8)
Statistics for all finalizable objects (including all objects ready for finalization):
      MT    Count    TotalSize Class Name
71b07520        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
71b074c8        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
71b06190        1           20 Microsoft.Win32.SafeHandles.SafeFileHandle
71aff62c        1           20 Microsoft.Win32.SafeHandles.SafePEFileHandle
001938a8        2           24 finalizequeue.MyClass
71b06058        1           44 System.Threading.ReaderWriterLock
71b00128        1           48 System.Threading.Thread
Total 8 objects

Ready for finalization(= Freachable キュー)に二つのオブジェクトが格納されていることがわかります。一般的に、このReady for finalizationが0以上の場合はアプリに問題がある場合が多々あります。
!finalizequeue -allReadyを実行すると、Freachableキューの中身を確認できます。MyClassオブジェクトが二つあることがわかります。
0:000> !finalizequeue -allReady
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 0 finalizable objects (007499b0->007499b0)
generation 1 has 0 finalizable objects (007499b0->007499b0)
generation 2 has 6 finalizable objects (00749998->007499b0)
Finalizable but not rooted: 
Ready for finalization 2 objects (007499b0->007499b8)
Statistics for all finalizable objects that are no longer rooted:
      MT    Count    TotalSize Class Name
001938a8        2           24 finalizequeue.MyClass
Total 2 objects
4. ファイナライザを実行するスレッドが今どのような状態化を調べます。
!threadsを実行するとファイナライザを実行しているスレッドがわかります。
0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                   PreEmptive   GC Alloc                Lock
       ID  OSID ThreadOBJ    State GC           Context       Domain   Count APT Exception
   0    1   aa8 0073e6b8   200a020 Enabled  00000000:00000000 00737e50     0 MTA
   2    2   6d4 0074a260   200b220 Enabled  00000000:00000000 00737e50     0 MTA (Finalizer)
5. ファイナライザを実行しているスレッドにコンテキストを変更し、そのスレッドのスタックを!clrstackを利用して見てみます。
0:000> ~2s
eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=046bf52c edi=00000000
eip=776afd71 esp=046bf4e8 ebp=046bf550 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!ZwDelayExecution+0x15:
776afd71 83c404          add     esp,4
0:002> !clrstack
OS Thread Id: 0x6d4 (2)
Child SP IP       Call Site
046bf610 776afd71 [HelperMethodFrame: 046bf610] System.Threading.Thread.SleepInternal(Int32)
046bf65c 71a538f9 System.Threading.Thread.Sleep(Int32)*** WARNING: Unable to verify checksum for mscorlib.ni.dll

046bf660 001f0213 finalizequeue.MyClass.Finalize()*** WARNING: Unable to verify checksum for finalizequeue.exe
[C:\Users\seven\documents\visual studio 2010\Projects\finalizequeue\finalizequeue\Program.cs @ 17]
046bf8a8 7273bdfd [DebuggerU2MCatchHandlerFrame: 046bf8a8]
スタックを見ると、MyClassのFinalize()を実行中であることがわかります。
6. !Finalize()のアセンブラを!Uを利用して見てみます。
0:002> !U 001f0213
Normal JIT generated code
finalizequeue.MyClass.Finalize()
Begin 001f01d0, size 61

C:\Users\seven\documents\visual studio 2010\Projects\finalizequeue\finalizequeue\Program.cs @ 14:
001f01d0 55              push    ebp
001f01d1 8bec            mov     ebp,esp
001f01d3 57              push    edi
001f01d4 56              push    esi
001f01d5 53              push    ebx
001f01d6 83ec1c          sub     esp,1Ch
001f01d9 33c0            xor     eax,eax
001f01db 8945e0          mov     dword ptr [ebp-20h],eax
001f01de 8945e4          mov     dword ptr [ebp-1Ch],eax
001f01e1 8945e8          mov     dword ptr [ebp-18h],eax
001f01e4 8945ec          mov     dword ptr [ebp-14h],eax
001f01e7 33c0            xor     eax,eax
001f01e9 8945e8          mov     dword ptr [ebp-18h],eax
001f01ec 894dd8          mov     dword ptr [ebp-28h],ecx
001f01ef 833d4431190000  cmp     dword ptr ds:[193144h],0
001f01f6 7405            je      001f01fd
001f01f8 e882627372      call    clr!JIT_DbgIsJustMyCode (7292647f)
001f01fd c745dc00000000  mov     dword ptr [ebp-24h],0
001f0204 90              nop
001f0205 90              nop
001f0206 eb0d            jmp     001f0215

C:\Users\seven\documents\visual studio 2010\Projects\finalizequeue\finalizequeue\Program.cs @ 17:
001f0208 90              nop
001f0209 b9e8030000      mov     ecx,3E8h
001f020e e8e1368671      call    mscorlib_ni+0x2738f4 (71a538f4) (System.Threading.Thread.Sleep(Int32), mdToken: 06001695)
>>> 001f0213 90              nop
001f0214 90              nop
001f0215 b801000000      mov     eax,1
001f021a 25ff000000      and     eax,0FFh
001f021f 8945dc          mov     dword ptr [ebp-24h],eax
001f0222 90              nop
001f0223 ebe3            jmp     001f0208

C:\Users\seven\documents\visual studio 2010\Projects\finalizequeue\finalizequeue\Program.cs @ 18:
001f0225 8b4dd8          mov     ecx,dword ptr [ebp-28h]
001f0228 e883158a71      call    mscorlib_ni+0x2b17b0 (71a917b0) (System.Object.Finalize(), mdToken: 06000008)
001f022d 90              nop

C:\Users\seven\documents\visual studio 2010\Projects\finalizequeue\finalizequeue\Program.cs @ 14:
001f022e 58              pop     eax
001f022f ffe0            jmp     eax

001f0223で001f0208にjmpしており、この間で無限ループしていることがわかります。
7. !dumpheap!objsizeを利用すると、MyClassのオブジェクトのサイズがわかります。
0:002> !dumpheap -type finalizequeue.MyClass
Address       MT     Size
024fca74 001938a8       12    
024fca80 001938a8       12    
024fca98 001938a8       12    
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
001938a8        3           36 finalizequeue.MyClass
Total 3 objects
0:002> !objsize 024fca74
sizeof(024fca74) =     40000024 (   0x2625a18) bytes (finalizequeue.MyClass)
0:002> !objsize 024fca80
sizeof(024fca80) =     40000024 (   0x2625a18) bytes (finalizequeue.MyClass)
0:002> !objsize 024fca98
sizeof(024fca98) =     40000024 (   0x2625a18) bytes (finalizequeue.MyClass)

よって「MyClassのファイナライザが無限ループしているため、Freachableキューのオブジェクトが削除されずメモリを圧迫した」ということがダンプ調査からわかります。

上記の状態をまとめると以下のような図になります。

補足
・ダンプからexeのイメージを抽出し、それをデコンパイラを利用してデコンパイルすることも可能です。
詳細は、[sos]!SaveModule:ダンプファイルからイメージを抽出をご参照ください。

リンク
・ガベージコレクタの基本とパフォーマンスのヒント
http://msdn.microsoft.com/ja-jp/library/ms973837.aspx
・Understanding when to use a Finalizer in your .NET class
http://blogs.msdn.com/b/tom/archive/2008/04/25/understanding-when-to-use-a-finalizer-in-your-net-class.aspx
・Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework
http://msdn.microsoft.com/en-us/magazine/bb985010.aspx
・MSDNによる!finalizequeue含むsos.dllの説明
http://msdn.microsoft.com/en-us/library/bb190764.aspx
・channel 9の人気のビデオ.NET Debugging Starter Kit for the Production Environmentでも!finalizequeueは紹介されていました。
http://channel9.msdn.com/Series/-NET-Debugging-Stater-Kit-for-the-Production-Environment/Common-Debugging-Commands-04


  1. 2012/05/20(日) 15:20:37|
  2. SOS・Psscor2/Psscor4
  3. | トラックバック:0
  4. | コメント:0
<<アセンブリバインドログビューア(Fuslogvw.exe)でアセンブリバインドの詳細表示 | ホーム | [sos]!ObjSize:オブジェクトのサイズを表示>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://troushoo.blog.fc2.com/tb.php/56-2b6d92ac
この記事にトラックバックする(FC2ブログユーザー)

スポンサーリンク

最新記事

月別アーカイブ

カテゴリ

ツール (92)
ネットワーク (76)
Visual Studio (56)
SOS・Psscor2/Psscor4 (25)
WinDbg (25)
Linux (24)
Azure (17)
Tips (20)
英語 (1)
About Me (1)
未分類 (0)

全記事表示リンク

全ての記事を表示する

検索フォーム

RSSリンクの表示

リンク

このブログをリンクに追加する

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。