[[Open棟梁Project>http://opentouryo.osscons.jp/]] - [[マイクロソフト系技術情報 Wiki>http://techinfoofmicrosofttech.osscons.jp/]] -[[戻る>メモリ・リーク]] * 目次 [#d5d0a567] #contents *概要 [#se5f4d1e] -マネージ・リソースであれば、GCで適切に解放される。~ -このため、マネージ・オブジェクトでリークするものは、~ 関数スタックに積まれるローカル変数以外に保持されたマネージ・オブジェクトである可能性が高い。~ *GC [#h73181b8] -.NETアプリを軽快にするためのガベージ・コレクション講座 - @IT~ http://www.atmarkit.co.jp/fdotnet/directxworld/directxworld06/directxworld06_01.html **アンマネージ・リソース [#da9cceb0] -アンマネージ・リソースを使用するマネージコードはリソースを明示的に解放する。 -これを怠ると、マネージコード上でGCが動作していてもメモリリークが発生する。 -マネージのユーザコードでアンマネージ・リソースをGCで適切に解放するには、以下の実装を施す必要がある。 ***Marshallクラス [#ec29aa50] -Marshallクラスを使用してアンマネージ・メモリ確保をする場合はメモリの使用後に明示的な開放が必要になるので注意する。~ (アンマネージ・メモリは.NETオブジェクトと異なりGC対象とならないため。) -Marshal クラス~ http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.marshal.aspx ***IDisposable インターフェイス [#rf487e78] -メンバにアンマネージ・リソースを持つ.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 [#t4a37614] -CLR 徹底解剖 大きなオブジェクト ヒープの秘密~ http://msdn.microsoft.com/ja-jp/magazine/cc534993.aspx >LOHの問題によってGen2の断片化に起因するメモリリークが発生することもある(.NET3.5までの問題)。 ***その他、LOH関係の参考資料 [#tc15305e] +The Dangers of the Large Object Heap~ http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/~ サンプル作成者のページ +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のフィードバック用ページ +.NET 航海日誌: LOH (ラージオブジェクトヒープ) でフラグメンテーションが起こる !?~ http://dotnetlogbook.blogspot.jp/2009/10/loh.html~ 上記(1)を、日本語で要約したページ +torutkの日記: .NETのメモリ管理と断片化問題~ http://d.hatena.ne.jp/torutk/20100529/p1 *SOS.dll [#mfaa3ea9] **WinDbg+SOS拡張によるマネージ・ライブ・デバック例 [#j1168cb9] ***開始 [#t544146b] -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)オブジェクト情報の確認方法>#d795b276]] --[[(2)メモリリークの確認方法>#z92b4751]] ***(1)オブジェクト情報の確認方法 [#d795b276] -指定したクラスのオブジェクトのみ一覧する。 !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に指定したオブジェクトのアドレスから~ メモリ情報([メモリ] ウィンドウ)を確認すると~ メンバ変数の情報を確認することができる。 --[メモリ] ウィンドウ~ http://msdn.microsoft.com/ja-jp/library/s3aw423e.aspx -同様に上記の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)メモリリークの確認方法 [#z92b4751] -!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>で、指定したオブジェクトに対するオブジェクトへの参照(ルート)情報が得られる。~ &color(red){★ この参照を辿って行くことで参照を握っているルートオブジェクトを確認できる。}; !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 ***参考資料 [#q3817704] -SOS.dll (SOS デバッガー拡張)~ http://msdn.microsoft.com/ja-jp/library/bb190764.aspx -方法 SOS を使用する~ http://msdn.microsoft.com/ja-jp/library/yy6d2sxs.aspx -WinDbg および SOS 拡張機能を使用して ASP.NET のトラブルシューティングします。~ https://support.microsoft.com/ja-jp/kb/892277