マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。

目次

概要

.NETのメモリ・リークについて、

ついて説明する。

GC

マネージ・リソース

  • マネージ・リソースであれば、GCで適切に解放される。
  • このため、マネージ・オブジェクトでリークするものは、
    関数スタックに積まれるローカル変数以外に保持された
    マネージ・オブジェクトである可能性が高い。

アンマネージ・リソース

  • アンマネージ・リソースを使用するマネージコードはリソースを明示的に解放する。
  • これを怠ると、マネージコード上でGCが動作していてもメモリリークが発生する。
  • マネージのユーザコードでアンマネージ・リソースをGCで適切に解放するには、以下の実装を施す必要がある。

Marshallクラス

Marshallクラスを使用してアンマネージ・メモリ確保をする場合は
メモリの使用後に明示的な開放が必要になるので注意する。
(アンマネージ・メモリは.NETオブジェクトと異なりGC対象とならないため。)

IDisposable インターフェイス

メンバにアンマネージ・リソースを持つ.NETオブジェクトには、
以下のIDisposableインターフェイスと対応する実装を施す必要がある。

  • 参考
    • IDisposable インターフェイス
      http://msdn.microsoft.com/ja-jp/library/system.idisposable.aspx

      IDisposableが適切に実装されていれば、例外時はGC任せで、
      (未DisposeのオブジェクトはFinalizeでDisposeされる)
      正常時にClose呼び出しをするだけで良い
      (Close内でDisposeを呼び出すのが慣例の実装のため)。

      • 例外的にCloseやファイナライザがあってもDisposeを明示的に呼び出す必要があるものもある
        (例えば、旧ODP.NET、今は修正されている)が、これは、IDisposableの実装方針に反している。
      • オブジェクトの世代によっては解放タイミングが遅れるためファイナライザの実行タイミングも遅れることになる。
        これが問題となるような場合は、ファイナライザ任せで処理を実装しないこと(Disposeを明示的に呼び出す)。

LOH

その他、LOH関係の参考資料

  1. The Dangers of the Large Object Heap
    http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/
    「ラージオブジェクトヒープの危険性」サンプル作成者のページ
  2. Large Object Heap fragmentation causes OutOfmemoryException? 報告者 Rudiger Klaehn
    http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception
    上記(1)のサンプル作成者が米Microsoftに bug として報告したMicrosoftのフィードバック用ページ
  3. .NET 航海日誌: LOH (ラージオブジェクトヒープ) でフラグメンテーションが起こる !?
    http://dotnetlogbook.blogspot.jp/2009/10/loh.html
    上記(1)を、日本語で要約したページ
  4. torutkの日記: .NETのメモリ管理と断片化問題
    http://d.hatena.ne.jp/torutk/20100529/p1

SOS.dll

WinDbg+SOS拡張によるマネージ・ライブ・デバック例

久しぶりに使ったので手順などをメモ(幾つか補足も記載)。

fileBook1.xlsx

開始

  • VSのイミディエイト・ウィンドウからもSOSデバッガ拡張コマンドを実行できる。
  • WinDbgから利用することもできるが、VSの環境変数を使う場合は、CMDから
    vsvars32.batを実行後にWinDbg.exeを起動して対象プロセスにアタッチする。

※ 同様の操作をマネージ・ダンプに対して行う事もできる。

以下はSOSデバッガ拡張コマンドの実行例。

  • .load sosコマンドを実行。
    .load sos
    マネージのみのデバッグ中は、SOS は使用できません。
    SOS を読み込むには、プロジェクト プロパティのアンマネージ デバッグを有効にしてください。
  • と表示されたら、プロジェクト プロパティのアンマネージ デバッグを有効にする。
    拡張 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll は読み込まれました
  • と表示されたら、!DumpHeap?コマンドを実行。
    !DumpHeap [<-stat>] [-min <size>] [-max <size>] [-thinlock]~
    [-mt <Method Table address>] [-type <partial type name>] [start[end]]
    • -stat:統計情報のみ。 → メモリリーク確認ではこれを使用する。
    • -min:指定サイズ以下のオブジェクトを無視。
    • -max:指定サイズ以上のオブジェクトを無視。
    • -thinlock:thinlockを報告する(ロックに利用)。
    • -mt:指定したMethod Tableに対応するオブジェクトのみ一覧。
    • -type:
      指定した型名(部分文字列可)に対応するオブジェクトのみ一覧。
      → 特定のクラスの確認ではこれを使用する。
    • start:指定したアドレスで一覧を開始。
    • end:指定したアドレスで一覧を終了。
  • !DumpHeap?コマンドでは、
    • ヒープのオブジェクト情報を一覧できる。
    • このコマンドは、メモリリークなどの問題を確認するのに有効である。
    • 本番環境で動作するメモリリークをデバッグする場合は、WinDbgなどのデバッガを使用すると良い(WindowsSDK?)。
    • また、-Typeオプションを指定することで、任意の型のオブジェクトのみ一覧できる。

(1)オブジェクト情報の確認方法

  • 指定したクラスのオブジェクトのみ一覧する。
    !DumpHeap -Type Class1
    
    PDB symbol for mscorwks.dll not loaded
     Address       MT     Size
    013ac060 009e71fc       20     
    total 1 objects
    Statistics:
          MT    Count    TotalSize Class Name
    009e71fc        1           20 WindowsFormsApplication1.Class1
    Total 1 objects
  • 上記のオブジェクトのアドレス情報を!DumpObj?に渡すと、次のようなオブジェクト情報が得られる。
    !DumpObj <Object address>
    • Name:クラス名
    • MethodTable?:メソッド・テーブル
    • EEClass:CLRクラス定義へのポインタ
    • Size:インスタンスのサイズ
    • Fields:各種フィールド情報
!DumpObj 013ac060

Name: WindowsFormsApplication1.Class1
MethodTable: 009e71fc
EEClass: 00f01fb8
Size: 20(0x14) bytes
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79332d70  4000006        4         System.Int32  1 instance        9 a
79332d70  4000007        8         System.Int32  1 instance        9 b
79332d70  4000008        c         System.Int32  1 instance        9 c
79332d70  4000009       24         System.Int32  1   static        9 x
79332d70  400000a       28         System.Int32  1   static        9 y
79332d70  400000b       2c         System.Int32  1   static        9 z

DumpObj?に指定したオブジェクトのアドレスから
メモリ情報([メモリ] ウィンドウ)を確認すると
メンバ変数の情報を確認することができる。

  • 同様に上記のEEClass(CLRで管理されるクラス定義情報へのポインタ)
    のアドレス情報を!DumpClass?に渡すと、次のようなクラス情報が得られる。
    !DumpClass <EEClass address>
    • Class Name:クラス名
    • Parent Class:親クラスのアドレス
    • Module:モジュールのアドレス
    • MethodTable?:メソッド・テーブル
    • Vtable Slots:-
    • Total Method Slots:-
    • Class Attributes:クラスの属性数
    • NumxFields:各種フィールド数と情報
!DumpClass 00f01fb8

Class Name: WindowsFormsApplication1.Class1
Parent Class: 790c3ef0
Module: 009e2c5c
Method Table: 009e71fc
Vtable Slots: 4
Total Method Slots: 6
Class Attributes: 100001  
NumInstanceFields: 3
NumStaticFields: 3
      MT    Field   Offset                 Type VT     Attr    Value Name
79332d70  4000006        4         System.Int32  1 instance           a
79332d70  4000007        8         System.Int32  1 instance           b
79332d70  4000008        c         System.Int32  1 instance           c
79332d70  4000009       24         System.Int32  1   static        9 x
79332d70  400000a       28         System.Int32  1   static        9 y
79332d70  400000b       2c         System.Int32  1   static        9 z

こちらでは、インスタンス変数の値は表示されない。
また、上記のフィールド情報はilasmを使用して確認できるらしいが、情報を確認できず。

  • Moduleのアドレスを!DumpModule?に渡すと、次のようなモジュール情報(PEFile)が得られる。
    !DumpModule <Modul address>
!DumpModule 009e2c5c

Attributes: PEFile 
Assembly: 001b2478
LoaderHeap: 00000000
TypeDefToMethodTableMap: 009e00c0
TypeRefToMethodTableMap: 009e00dc
MethodDefToDescMap: 009e0194
FieldDefToDescMap: 009e01d4
MemberRefToDescMap: 009e0204
FileReferencesMap: 009e02dc
AssemblyReferencesMap: 009e02e0
MetaData start address: 00402440 (4084 bytes)
  • !DumpModule? コマンドの出力に確認できる
    FieldDefToDescMap?から静的フィールドの格納位置がわかるとのこと。
    クラスレイアウトについて
    インスタンス
    └メソッドテーブル ┬ EEClass ─ BaseClass
                       ├ MT of BaseClass
                       └ Module ┬ Field Descriptor Map
                                 └ TypeDef To Method Table Map
  • !DumpObj?、!DumpClass?コマンドの出力に確認できる
    Method Tableのアドレスを!DumpMTに渡すと、Method Table情報を確認できる。
    !DumpMT -md <Method Table address>(-mdを付与しないと一覧情報が出力されない)
    • JIT列に表示される情報の意味は以下の通り。
      • PreJIT:プリコンパイル済み。
      • JIT:JITコンパイル済み。
      • NONE:JITコンパイルが行われていない。
!DumpMT -md 009e71fc

EEClass: 00f01fb8
Module: 009e2c5c
Name: WindowsFormsApplication1.Class1
mdToken: 02000006 BaseSize: 0x14
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
79286aa0   79104944   PreJIT System.Object.ToString()
79286ac0   7910494c   PreJIT System.Object.Equals(System.Object)
79286b30   7910497c   PreJIT System.Object.GetHashCode()
792f7510   791049a0   PreJIT System.Object.Finalize()
009ec160   009e71e4      JIT WindowsFormsApplication1.Class1..ctor()
009ec168   009e71f0      JIT WindowsFormsApplication1.Class1..cctor()

(2)メモリリークの確認方法

  • !DumpHeap? -statで、マネージヒープの統計情報が出力される。
    !DumpHeap -stat
    
    total 10311 objects
    Statistics:
          MT    Count    TotalSize Class Name
    ・・・
    009e6c90        1           20 WindowsFormsApplication1.Class1
    ・・・
    009e5e50        1          332 WindowsFormsApplication1.Form1
  • !DumpHeap? -mt <Method Table address>で、指定したクラスに対する各インスタンス情報が得られる。
    !DumpHeap -mt 009e5e50
    
     Address       MT     Size
    013bd8f4 009e5e50      332     
    total 1 objects
    Statistics:
          MT    Count    TotalSize Class Name
    009e5e50        1          332 WindowsFormsApplication1.Form1
    Total 1 objects
  • !gcroot <Object address>で、指定したオブジェクトに対するオブジェクトへの参照(ルート)情報が得られる。
    ★ この参照を辿って行くことで参照を握っているルートオブジェクトを確認できる。
    !gcroot 013bd8f4
    
    Note: Roots found on stacks may be false positives. Run "!help gcroot" for
    more info.
    ebx:Root:013be34c(System.Windows.Forms.Application+ThreadContext)->
    013bd8f4(WindowsFormsApplication1.Form1)
    esi:Root:013e0b2c(System.Windows.Forms.Application+ComponentManager+ComponentHashtableEntry)->
    013be34c(System.Windows.Forms.Application+ThreadContext)
    edi:Root:013eb4a0(System.Collections.Hashtable+HashtableEnumerator)->
    013e0b2c(System.Windows.Forms.Application+ComponentManager+ComponentHashtableEntry)
    Scan Thread 0 OSTHread 1a30
    ESP:12f358:Root:013be34c(System.Windows.Forms.Application+ThreadContext)->
    013e0b2c(System.Windows.Forms.Application+ComponentManager+ComponentHashtableEntry)
    ESP:12f360:Root:013e0ae4(System.Windows.Forms.Application+ComponentManager)->
    013be34c(System.Windows.Forms.Application+ThreadContext)
    ・・・
    013dc328(Microsoft.Win32.UserPreferenceChangedEventHandler)->
    013bd8f4(WindowsFormsApplication1.Form1)
    DOMAIN(00159EE0):HANDLE(Pinned):9c13ec:Root:02333250(System.Object[])->
    013be278(System.Collections.Hashtable)->
    013be2b0(System.Collections.Hashtable+bucket[])->
    013be34c(System.Windows.Forms.Application+ThreadContext)
  • その他、以下のコマンドを利用できる。
  • !eeheap -gcで、GCの情報が得られる。
    !eeheap -gc
    
    Number of GC Heaps: 1
    generation 0 starts at 0x01331018
    generation 1 starts at 0x0133100c
    generation 2 starts at 0x01331000
    ephemeral segment allocation context: none
     segment    begin allocated     size
    01330000 01331000  013ebff4 0x000baff4(765940)
    Large object heap starts at 0x02331000
     segment    begin allocated     size
    02330000 02331000  02336de8 0x00005de8(24040)
    Total Size   0xc0ddc(789980)
    ------------------------------
    GC Heap Size   0xc0ddc(789980)
  • !objsize <Object address>で、参照先オブジェクトも含めたサイズ
    !objsize 013bd8f4
    
    sizeof(013bd8f4) =        13420 (      0x346c) bytes (WindowsFormsApplication1.Form1)
    ※ 単体:332、参照先を含める:13420
  • !finalizequeueで、ファイナライザキューにたまっているオブジェクトを一覧
    !finalizequeue
    
    SyncBlocks to be cleaned up: 0
    MTA Interfaces to be released: 0
    STA Interfaces to be released: 0
    ----------------------------------
    generation 0 has 360 finalizable objects (001e7090->001e7630)
    generation 1 has 0 finalizable objects (001e7090->001e7090)
    generation 2 has 0 finalizable objects (001e7090->001e7090)
    Ready for finalization 0 objects (001e7630->001e7630)
    Statistics:
          MT    Count    TotalSize Class Name
    7b2252dc        1           16 System.Windows.Forms.Control+FontHandleWrapper
    7b226088        1           20 System.Windows.Forms.ApplicationContext
    79321428        1           20 Microsoft.Win32.SafeHandles.SafePEFileHandle
    7ae3ca3c        1           24 System.Drawing.Bitmap
    7b226160        1           28 System.Windows.Forms.Cursor
    ・・・
    009e5e50        1          332 WindowsFormsApplication1.Form1
    7b2238c4       26          728 System.Windows.Forms.Internal.WindowsGraphics
    7b21a930       20          960 System.Windows.Forms.Internal.WindowsPen
    7932a41c       87         1392 System.WeakReference
    7b223a00       26         1872 System.Windows.Forms.Internal.DeviceContext
    Total 360 objects
  • * e !clrstackで、全てのマネージスレッドとスタックトレースし、ファイナライザスレッドのハングを確認する。

    ~* e !clrstack
    
    OS Thread Id: 0x1a30 (0)
    ESP       EIP     
    0012f32c 7c94e514 [InlinedCallFrame: 0012f32c] System.Windows.Forms.UnsafeNativeMethods.WaitMessage()
    0012f328 7b1d8ed8 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
    0012f3c4 7b1d89c7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
    0012f418 7b1d8811 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
    0012f448 7b195921 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
    0012f45c 00f300ae WindowsFormsApplication1.Program.Main()
    0012f688 79e71b4c [GCFrame: 0012f688] 
    
    OS Thread Id: 0x1a44 (1)
    Unable to walk the managed stack. The current thread is likely not a 
    managed thread. You can run !threads to get a list of managed threads in
    the process
    
    OS Thread Id: 0x16c8 (2)
    Failed to start stack walk: 80004005
    
    OS Thread Id: 0xa14 (3)
    Unable to walk the managed stack. The current thread is likely not a 
    managed thread. You can run !threads to get a list of managed threads in
    the process
    
    OS Thread Id: 0x11fc (4)
    Unable to walk the managed stack. The current thread is likely not a 
    managed thread. You can run !threads to get a list of managed threads in
    the process
  • !Threads -special
    !Threads -special
    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 1a30 00161b98      6020 Enabled  013eb4c4:013ebfe8 00159ee0     0 STA
       2    2 16c8 0016e360      b220 Enabled  00000000:00000000 00159ee0     0 MTA (Finalizer)
    
           OSID     Special thread type
       1   1a44    DbgHelper 
       2   16c8    Finalizer

参考資料

Silverlightの場合

開始

WinDbgとSilverlightをインストールしておく。

オブジェクト情報の確認方法

以下WinDbgにてSilverlightのダンプファイルから、オブジェクト数を確認する手順の例。
アタッチ先を「iexplore.exe」などに設定すれば、ライブ・デバックも可能であるもよう。

  • メニュー[File]-[Open Crush Dump]からダンプファイルを開く
  • 以下コマンドでSOSを利用可能とする
    .load C:\Program Files\Microsoft Silverlight\4.1.10329.0\sos.dll
    ※パスは環境(インストール・バージョン、インストール先)に応じて修正してください
  • 以下コマンドで全てのオブジェクト数を取得する
    !dumpheap -stat
  • 以下コマンドでSilverlightアプリ内で定義したクラスのオブジェクト数を取得する
    !dumpheap -stat -type XXXXXXX
  • 他のダンプファイルを解析する際は、再度WinDbgを実行する。

Silverlight Toolkitの問題

Silverlight Toolkitにメモリリークの問題があります。

We encountered this bug too, but took a slightly different tact -- we used a weak event listener pattern.

  • Instead of:
               if (null != _rootVisual)
               {
                   // Ideally, this would use AddHandler(MouseMoveEvent), but MouseMoveEvent doesn't exist
                   _rootVisual.MouseMove += new MouseEventHandler(HandleRootVisualMouseMove);
               }
  • We used:
               if (null != _rootVisual)
               {
                   var rootVisual = _rootVisual;
    
                   // Use a weak event listener.
                   var rootVisualMouseMoveListener = new WeakEventListener<ContextMenu, object, MouseEventArgs>( this );
                   rootVisualMouseMoveListener.OnEventAction = ( instance, source, eventArgs ) => instance.HandleRootVisualMouseMove( source, eventArgs );
                   rootVisualMouseMoveListener.OnDetachAction = ( weakEventListener ) => rootVisual.MouseMove -= weakEventListener.OnEvent;
                   rootVisual.MouseMove += rootVisualMouseMoveListener.OnEvent;
               }

ここの「we used:」が修正コード。

参考資料

その他

Visual Studio Enterprise Edition

Visual Studio2015 Enterprise Edition ではマネージ メモリの分析機能が
強化されており、!DumpHeap? コマンドで実現できることに近い内容が確認できる。

その他の拡張

.NET Framework アプリケーションのデバッグに有用
(ただし、サポートは提供されておらずドキュメントや公開情報も少ない状況)

psscor4

.load "C:\\Psscor4\\x86\\x86\\psscor4.dll"
!psscor4.help

mex

.load mex.dll
!mex.help

Tags: :障害対応, :性能, :デバッグ


添付ファイル: fileBook1.xlsx 726件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-10-07 (月) 16:54:32 (62d)