de:code 2019 [DT03] 上級サポート エンジニアの経験お伝えします：Visual Studio 2019 でメモリー リークを追え!

Microsoft de:code 2019に参加した内容を紹介します。

もし間違いなどあれば、ご指摘いただけますと助かります。

# [DT03] 上級サポート エンジニアの経験お伝えします：Visual Studio 2019 でメモリー リークを追え! / 2019年5月29日

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/faec4264-d742-cea0-ea39-11f7d0d70434.png)

# TL;DR（要約）

- メモリリークといっても、エラー（「コンピューターのメモリが不足しています」、「OutOfMemory」）と遅延（システムやアプリケーションが「なんだがわからないが動かない」）の2種類がある。
- 原因調査のためには、パフォーマンスカウンターで「Private BytesとVirtual Bytes とWorkingsets」を調べる。
- 大幅な高速化とメモリ使用量が改善したVisual Studio 2019の診断ツールを使用することで、どのイベント（メソッド）でメモリリークが起きているかを追いかけることができる。

# リソース

- [セッション詳細](https://www.microsoft.com/ja-jp/events/decode/2019session/detail.aspx?sid=DT03&kw=DT03)
- [セッション動画](https://www.youtube.com/watch?v=qYCe_EeodzU&feature=youtu.be)

# 講師はどのような方？

vb4.0からサポートを行ってきた人、サポートを20年以上！

# 今日持ち帰っていただきたいこと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/de19a2a2-fdb2-884b-063b-3cc46dc951fa.png)

- メモリリークって何？
- メモリーリークが起きる「前に」しておくこと
- メモリーリークが起きたらすること
- Visual Studioもやるじゃん！

# 関連資料

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/cbc58ae3-21f5-bf57-f79b-f60f080f0aa1.png)

http://aka.ms/decode19DT03


# メモリーリークが発生しているというエラーは見たことがある？

メモリーリークといいますけど、メモリーリークが発生しています！というエラーは起きないですよね。

では、実際にはどういったエラーを見るのか？

## コンピューターのメモリが不足しています

それは、「コンピューターのメモリが不足しています」というメッセージ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/554484fe-7f05-e82d-8166-be2e0c9e4381.png)

## OutOfMemory！

または、OutOfMemoryExceptionのエラーが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/7c99376e-1add-e91d-fc8e-bc7849c40d84.png)

## なんだがわからないが動かない

または、なんだがわからないが動いていない、といわれる。アプリケーションの遅延、システム全体の遅延が発生する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/9438b79c-37de-ae77-1128-c5b5602174ad.png)


# では、どうやって解決していくのか

実際に起きてみてわかることだが、そもそもメモリー使用量とはなんなのか？

問い合わせをもらうと、「まず最初にいつもはどうですか、普通はどうなのか」と聞くんですけど、普通って何か、ということがわからないこともよくある

- そもそも普通というのはどう考えればよいのか
- メモリー使用量内訳はどうやって調べればいいのか
- 怪しいところをどうやって見つけるのか

こういったシナリオをベースに話していく

# 実際に試してみよう

新しいプロジェクト、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が制御している。

コンピュータのメモリが不足していますというウインドウは、物理＋仮想も足りなくなった場合！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/554484fe-7f05-e82d-8166-be2e0c9e4381.png)

しかし、今回はOutOfMemory！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/7c99376e-1add-e91d-fc8e-bc7849c40d84.png)


## 解決方法

ではサポートが伝えるのはどういうところかというと、

### パフォーマンスカウンターで「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などがある。

## 調査方法

### ダンプ出力

```posh
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でやろう！

1. Visual Studioにドラッグアンドドロップ（テキストエディタ部分）
2. マネージドメモリをクリックするとヒープを分析
3. 右上のマイコードのみのチェックを外すと、呪文を唱えなくても確認することができる
4. 静的変数のform1.dというところまでわかる！！

### 方法3：Visual Studioの診断ツール！

あまり使い道がわかっていない人も多いと思うが、**意外と役に立つやつである！**

ダンプとは異なるが、スナップショットを作成することができる。

#### 実際のデモ

1. 処理ごとにスナップショットを取ってみる（メモリ使用量タブ）
2. すると、スナップショットごとにどのくらい変わったということがわかる。例えば、オブジェクトの数が3個なのに大きい！
3. ヒープサイズのところをクリックすると、内容が見ることができる。そして、比較対象として、スナップショット同士を比較することができる

**デバッグ実行しながら、スナップショットの比較を行うことができる**

### 方法4：デバッグからパフォーマンスプロファイラを呼び出す

「デバッグ実行って重いんでしょ？運用に入っているんだけど。。。」という疑問に対する回答となる。

- プロファイラメソッドで対象を観測することができる。GCの強制もできる
- 取集を止めると比較することができる

後は、方法3と同じような形で比較ができる

#### ではどこでおこったのか？

割り当て追跡を使用する。どのイベント（メソッド）で確保されたのかまでツリーを展開して追いかけることができる

# 補足

## Visual Studio自体が非常に軽くなったことで、使えるものとなった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/1ccf65f2-5157-2c75-1854-52dd1e8a97d7.png)

Visual Studio 2019になってから、Visual Studko 2019のメモリ使用量がだいぶ減った。

～Visual Studio 2017では、3GBのダンプファイルを食わせると死んだりしていたので、実質使用に耐えなかった。今は、3GBのダンプファイルを食わせても、300MBしか使用しない。

**要は、Visual Studio 2019でメモリ使用量が減ったことで現実的に使えるようになってきた。**

# まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204150/179ecd27-2b75-be23-b815-a2ba819e9825.png)

## メモリーリーク

仮想メモリ、メモリ空間、マネージドメモリー、GC

## メモリーリークが起きる「前に」しておくこと

obuservablitlity、GCキックを仕込んでおく（観測のために）、情報採取方法を確認しておく

## メモリーリークが起きたらすること

タスクマネージャー、パフォーマンスカウンター、プロファイル

---

# 新しい時代に、新しいツールを！