Microsoft de:code 2019に参加した内容を紹介します。
もし間違いなどあれば、ご指摘いただけますと助かります。
[DT03] 上級サポート エンジニアの経験お伝えします:Visual Studio 2019 でメモリー リークを追え! / 2019年5月29日
TL;DR(要約)
- メモリリークといっても、エラー(「コンピューターのメモリが不足しています」、「OutOfMemory」)と遅延(システムやアプリケーションが「なんだがわからないが動かない」)の2種類がある。
- 原因調査のためには、パフォーマンスカウンターで「Private BytesとVirtual Bytes とWorkingsets」を調べる。
- 大幅な高速化とメモリ使用量が改善したVisual Studio 2019の診断ツールを使用することで、どのイベント(メソッド)でメモリリークが起きているかを追いかけることができる。
リソース
講師はどのような方?
vb4.0からサポートを行ってきた人、サポートを20年以上!
今日持ち帰っていただきたいこと
- メモリリークって何?
- メモリーリークが起きる「前に」しておくこと
- メモリーリークが起きたらすること
- Visual Studioもやるじゃん!
関連資料
メモリーリークが発生しているというエラーは見たことがある?
メモリーリークといいますけど、メモリーリークが発生しています!というエラーは起きないですよね。
では、実際にはどういったエラーを見るのか?
コンピューターのメモリが不足しています
それは、「コンピューターのメモリが不足しています」というメッセージ!
OutOfMemory!
または、OutOfMemoryExceptionのエラーが表示される。
なんだがわからないが動かない
または、なんだがわからないが動いていない、といわれる。アプリケーションの遅延、システム全体の遅延が発生する。
では、どうやって解決していくのか
実際に起きてみてわかることだが、そもそもメモリー使用量とはなんなのか?
問い合わせをもらうと、「まず最初にいつもはどうですか、普通はどうなのか」と聞くんですけど、普通って何か、ということがわからないこともよくある
- そもそも普通というのはどう考えればよいのか
- メモリー使用量内訳はどうやって調べればいいのか
- 怪しいところをどうやって見つけるのか
こういったシナリオをベースに話していく
実際に試してみよう
新しいプロジェクト、Visual Studio 2019でC#のプロジェクトで試してみる。
今回は意図的に!
内容
- static dictionaryを用意
- ボタンクリックを契機に、byte配列を作成したdirectoryに追加。Guidのランダムキーで100MB程度追加するだけのコード
実践
連打すると OutOfMemory Exceptionが発生した。
【お題】C# で8GBメモリのマシンでOutOfMemoryが発生した。どこまで増やせばよいか?
分析
実際に見てみると、16MBしか使っていない???
なぜか?
Windowsは仮想メモリーを使用している。8GB+スワップファイル24GBで仮想メモリとなっている。
各プロセスのメモリ空間は次のようになっている
プロセスの種類 | メモリ空間上限 |
---|---|
32bitOS | 2GB |
64bitOS 32bitプロセス | 4GB |
64bitOS 64bitプロセス | 8TB |
プロセスが使用しているメモリ空間が多くても、実際に物理メモリを使っているのは数MBとなる。使用しているメモリをメモリ仮想メモリのどちらに置くかはOSが制御している。
コンピュータのメモリが不足していますというウインドウは、物理+仮想も足りなくなった場合!
しかし、今回はOutOfMemory!
解決方法
ではサポートが伝えるのはどういうところかというと、
パフォーマンスカウンターで「Private BytesとVirtual Bytes とWorkingsets」を調べてください
この3つはprocessのオブジェクトグループにある。しかも対象のプロセスを選ぶことができる
グラフのスケールを調整(リセット)し、Private Bytesを見ると3GB使っていることが分かった。
→ということは、Processのメモリ空間が足りなくなってOutOfMemoryが発生したということがわかる
名称 | 意味 |
---|---|
Private Bytes | メモリ空間の使用量 |
Virtual Bytes | メモリ空間の予約量と使用量 |
Workingsets | 物理メモリで実際に使用しているサイズ |
タスクスケジューラでも、ワーキングセットとコミットサイズ(private bytes)を見ることができる
答え
一般的にプロセスのメモリー空間不足が原因。
システム全体の不足ではありません。
物理メモリーが足りなくなれば、システム全体に影響が出る。
【お題】C#で関数を抜けたのにプライベート関数のメモリーが解放されません。バグですか
分析
100MB追加し、100MB取り除くというコードを書いてみた。
知識
.NETではメモリーを大きく予約し(セグメント)、ちょっとずつ使います。
予約するセグメントの大きさ
OS | 予約セグメントのサイズ |
---|---|
32bit | 16MB~64MB |
64bit | 128MB~4GB |
ガベージコレクタの動作
- すべてのオブジェクトはいったん不要なものとみなし、参照されていないものを捨てる
- ガベージコレクタは自律的に動く
- ガベージコレクタは、世代管理する
- ガベージコレクタが不要なオブジェクトを片付けていく。
- ファイナライザは管理されていないオブジェクト(案マネージドリソース)を片付けるもの。ファイナライザが片付けて、再度ガベージコレクタしてやっと捨てられることになる
回答
ガベージコレクタは気分次第(いつ動くかどうかはわからない)
なので、OSが良きに計らうのでそのままとしておくのがよい。
GC.Collectを行うということはアプリケーションの通常動作としてやってはいけない。ガベージコレクタが動作すること自体に、コストがかかる(全スレッドを停止する)ため。
ただし、デバッグ用(本当に使用しているメモリーを知るためのみ)にそういう機能を裏で設ける、ということは有効な方法である。
【お題】プロセスがどんなオブジェクトでメモリーを使用しているか知りたいです。どんな方法がありますか
回答
昔からの方法:ダンプファイルをとりましょう!
今なら:Visual Studioのパフォーマンスプロファイラを使いましょう!
説明
プロセスのメモリ空間について
プロセスのメモリ空間には、exe、dllなどの アセンブリ、ヒープメモリ、スレッド、heap、 managed heap、Managed Threadなどがある。
調査方法
ダンプ出力
procdump.exe -ma -a -r (調査対象).exe c:\(ダンプファイル名).dmp
-ma -a -r
。このオプションを追加することで、プロセスが止まらない。
出力したファイルをどうやって解析するか
方法1:昔ながらの方法(WinDbgで解析する)
- アドレスを順番に解析していくと、Dictionaryで参照しているというところまで見ることができる
- Dictionaryのサイズについても確認することができる。
- サイズについても確認することができる
- 原因となっているものの特定方法
- 全体から詳細へとし、大きいものを探す
- 特定のセグメント、包含するオブジェクトが多いもの(DtaSet, DataTable HashTable)
- それでもわからないなら数が多いもの
問題点
サイズが大きいことはわかるが、なぜ多くなったのか、はわからない。
すでにリークしていれば、オブジェクトの数は膨大であり、GC待ちのものも存在する(このために、GC.Collectを行う裏スイッチを設けておく、という方法がある)
今ではお勧めしない
WinDbgでのダンプ解析は30分かかるが、時間泥棒な割に実入りが少ない。
昔は見たけど今はお勧めしない
現在のやり方
方法2:Visual Studioでやろう!
- Visual Studioにドラッグアンドドロップ(テキストエディタ部分)
- マネージドメモリをクリックするとヒープを分析
- 右上のマイコードのみのチェックを外すと、呪文を唱えなくても確認することができる
- 静的変数のform1.dというところまでわかる!!
方法3:Visual Studioの診断ツール!
あまり使い道がわかっていない人も多いと思うが、意外と役に立つやつである!
ダンプとは異なるが、スナップショットを作成することができる。
実際のデモ
- 処理ごとにスナップショットを取ってみる(メモリ使用量タブ)
- すると、スナップショットごとにどのくらい変わったということがわかる。例えば、オブジェクトの数が3個なのに大きい!
- ヒープサイズのところをクリックすると、内容が見ることができる。そして、比較対象として、スナップショット同士を比較することができる
デバッグ実行しながら、スナップショットの比較を行うことができる
方法4:デバッグからパフォーマンスプロファイラを呼び出す
「デバッグ実行って重いんでしょ?運用に入っているんだけど。。。」という疑問に対する回答となる。
- プロファイラメソッドで対象を観測することができる。GCの強制もできる
- 取集を止めると比較することができる
後は、方法3と同じような形で比較ができる
ではどこでおこったのか?
割り当て追跡を使用する。どのイベント(メソッド)で確保されたのかまでツリーを展開して追いかけることができる
補足
Visual Studio自体が非常に軽くなったことで、使えるものとなった。
Visual Studio 2019になってから、Visual Studko 2019のメモリ使用量がだいぶ減った。
~Visual Studio 2017では、3GBのダンプファイルを食わせると死んだりしていたので、実質使用に耐えなかった。今は、3GBのダンプファイルを食わせても、300MBしか使用しない。
要は、Visual Studio 2019でメモリ使用量が減ったことで現実的に使えるようになってきた。
まとめ
メモリーリーク
仮想メモリ、メモリ空間、マネージドメモリー、GC
メモリーリークが起きる「前に」しておくこと
obuservablitlity、GCキックを仕込んでおく(観測のために)、情報採取方法を確認しておく
メモリーリークが起きたらすること
タスクマネージャー、パフォーマンスカウンター、プロファイル