「[[マイクロソフト系技術情報 Wiki>http://techinfoofmicrosofttech.osscons.jp/]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。

-[[戻る>メモリ・リーク]]

* 目次 [#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]

久しぶりに使ったので手順などをメモ。

&ref(Book1.xlsx);

***開始 [#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

**Silverlightの場合 [#s8f064f9]
***開始 [#o0a0dc92]
WinDbgとSilverlightをインストールしておく。

***オブジェクト情報の確認方法 [#z9d555c0]
以下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の問題 [#s31e28d2]
Silverlight Toolkitにメモリリークの問題があります。

-Silverlight Toolkit - View Issue #7089:~
ContextMenu causes memory leak~
http://silverlight.codeplex.com/workitem/7089

>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:」が修正コード。

***参考資料 [#e8d190e8]
-[Silverlight]メモリリークの原因を調べる - 秘密結社ぎゅう☆ぎゅう倶楽部~
http://ushi.donburi.net/archives/2009/12/2009120101.html

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


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS