troushoo

スポンサーサイト

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

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

[sos]!GCRoot:オブジェクトを参照しているオブジェクトのパスを表示

WinDbg等で使用できる.NET用のデバッガエクステンションsosの!GCRootについて記述します。
!GCRootを使用すると、メモリリークが疑われる際に、なぜオブジェクトがガーベッジコレクションで解放されないかの原因がわかりえます。今回はメモリリークが発生したWPFアプリを利用して、!GCRootの使用例を記述します。

[!GCRoot コマンド]
!GCRootは引数にオブジェクトのアドレスをとり、そのオブジェクトを参照しているオブジェクトのパスを表示します。

[!GCRoot使用例]
-メモリリーク発生WPFアプリ
サンプルアプリとして、MainWindowのテキストボックスに何回文字を入力したかをSubWindowに表示するWPFアプリケーションを利用します。


詳細なコードについては後述しますが、今回のポイントの部分は以下です。

MainWindow.mw.textBox1.TextChanged += new TextChangedEventHandler(this.myTextChanged);
TextChangedEventHandlerを登録したが、解除し忘れてしまったために、SubWindowを閉じた後もSubWindowオブジェクトが解放されず、メモリリークが起きてしまったというアプリです。
SubWindowを閉じた後(すなわち正常ならSubWindowオブジェクトが解放されるべき後)のダンプをとります。

- ダンプ調査
ダンプをWinDbgを使って調査します。
以下のログはWindows 8 Customer Previewでの結果です。
1. WinDbgでダンプを開きます。
2. シンボルの設定をして、sosを読み込みます。
0:000> .symfix C:\sym
0:000> .reload
................................................................

Loading unloaded module list
.
0:000> .loadby sos clr
3. !DumpHeap -stat を利用して、何がヒープメモリを食っているかを調査します。
0:000> !DumpHeap -stat
Statistics:
      MT    Count    TotalSize Class Name
73f57130        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.Guid, mscorlib]]
~略~
73f27dfc     4432       189076 System.String
73f261bc     5491       266048 System.Object[]
73f2680c      382      2019420 System.Int32[]
Total 70035 objects
System.Int32[]がメモリを食っていることがわかりました。
4. !DumpHeap -type System.Int32[] を利用して、一番ヒープメモリを食っているSystem.Int32[]の詳細を調べてみます。
0:000> !DumpHeap -type System.Int32[]
Address       MT     Size
03451e20 73f2680c      300    
~略~
03603c64 73f2680c       16    
04462310 73f2680c  2000012    

Statistics:
      MT    Count    TotalSize Class Name
73f33abc        4           80 System.Int32[][]
73f2680c      382      2019420 System.Int32[]
Total 386 objects
5. 一番メモリを食っているオブジェクトに対し!GCRootをします。
0:000> !GCRoot 04462310
Thread fdc:
*** WARNING: Unable to verify checksum for WindowsBase.ni.dll
    00e3ee10 72ea5e74 DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
        ebp-c: 00e3ee4c
            ->  0345ae18 System.Windows.Threading.Dispatcher
            ->  036669d8 System.EventHandler
            ->  03666988 System.Object[]
            ->  034c1b70 System.EventHandler
            ->  034c0eb4 System.Windows.Media.MediaContext
            ->  034c104c System.Collections.Generic.Dictionary`2[[System.Windows.Media.ICompositionTarget, PresentationCore],[System.Object, mscorlib]]
            ->  03518484 System.Collections.Generic.Dictionary`2+Entry[[System.Windows.Media.ICompositionTarget, PresentationCore],[System.Object, mscorlib]][]
            ->  03549644 System.Windows.Interop.HwndTarget
            ->  03486614 gcroot.MainWindow
            ->  035633b0 System.Windows.EffectiveValueEntry[]
            ->  0354b46c System.Collections.Generic.List`1[[System.Windows.DependencyObject, WindowsBase]]
            ->  0354b484 System.Object[]
            ->  0354b82c System.Windows.Controls.Border
            ->  0354bacc System.Windows.Documents.AdornerDecorator
            ->  0354c050 System.Windows.Controls.ContentPresenter
            ->  035b6eb0 System.Windows.EffectiveValueEntry[]
            ->  0348ef8c System.Windows.Controls.Grid
            ->  03492f98 System.Windows.Controls.UIElementCollection
            ->  03492fac System.Windows.Media.VisualCollection
            ->  034c2c68 System.Object[]
            ->  03493080 System.Windows.Controls.TextBox
            ->  035b8454 System.Windows.EffectiveValueEntry[]
            ->  034be98c System.Windows.EventHandlersStore
            ->  035b6d60 MS.Utility.SixObjectMap
            ->  03516768 MS.Utility.FrugalObjectList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]]
            ->  03516774 MS.Utility.SingleItemList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]]
            ->  03516748 System.Windows.Controls.TextChangedEventHandler
            ->  0350c624 gcroot.SubWindow
            ->  04462310 System.Int32[]

Found 1 unique roots (run '!GCRoot -all' to see all roots).

上記より、一番メモリを食っているSystem.Int32[]オブジェクトはSubWindowから参照されており、またSubWindowはTextChangedEventHandlerから参照されていることがわかります。
これは、TextChangedEventHandlerを解放していないから起こります。
これにより、なぜInt32[]がGCで解放されないかは、TextChangedEventHandlerを解放していないからだということがわかります。

- メモリリークの対処法
本メモリリークの対処法はTextChangedEventHandlerの解除です。
たとえばSubWindowがアンロードされるときに以下のコードが呼び出されるようにします。

private void MyUnloaded(object sender, RoutedEventArgs e)
{
 MainWindow.mw.textBox1.TextChanged -= new TextChangedEventHandler(this.myTextChanged);
}

- 補足
・TextChangedEventHandlerをきちんと解除した場合で、上記の手順をとった後の!GCRootは以下のようになります。
0:000> !GCRoot 026cadec
    Found 0 unique roots (run '!GCRoot -all' to see all roots).

- ソースコード
・MainWindow.xaml.csのコード

public partial class MainWindow : Window
{
    public static MainWindow mw;
    public MainWindow()
    {
        InitializeComponent();
        mw = this;
        SubWindow sw = new SubWindow();
    }
}
・SubWindow.xaml.csのコード
public partial class SubWindow : Window
{
    int[] dummy;
    int counter;
    public SubWindow()
    {
        InitializeComponent();
        counter = 0;
        dummy = new int[500000];
        MainWindow.mw.textBox1.TextChanged += new TextChangedEventHandler(this.myTextChanged);
        this.myTextChanged(MainWindow.mw.textBox1, null);
        this.Show();
    }
    private void myTextChanged(object sender, RoutedEventArgs e)
    {
        this.label1.Content = "テキスト変更" + counter + "回目";
        counter++;
    }
    private void MyUnloaded(object sender, RoutedEventArgs e)
    {
        MainWindow.mw.textBox1.TextChanged -= new TextChangedEventHandler(this.myTextChanged); //メモリリーク発生時はここの部分をコメントアウトしました。
    }
}

リンク
・SOS.dll (SOS Debugging Extension)
MSDNによる!GCRoot含むsos.dllの説明
http://msdn.microsoft.com/en-us/library/bb190764.aspx
・Finding Memory Leaks in WPF-based applications
WPFのメモリリークについてまとまっているMSDNのブログ
http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx

・Memory Leak Detection in .NET
.NETのメモリリークについてまとまっているCODE PROJECTのページ
http://www.codeproject.com/Articles/19490/Memory-Leak-Detection-in-NET


  1. 2012/03/20(火) 13:58:52|
  2. SOS・Psscor2/Psscor4
  3. | トラックバック:0
  4. | コメント:0
<<IntelliTrace Collectorで本番環境のIntelliTraceデータの収集 | ホーム | Visual Studioでプロファイル(階層の相互作用のプロファイル)>>

コメント

コメントの投稿


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

トラックバック

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

スポンサーリンク

最新記事

月別アーカイブ

カテゴリ

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

全記事表示リンク

全ての記事を表示する

検索フォーム

RSSリンクの表示

リンク

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

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