こんにちは、サイオステクノロジー技術部 武井です。今回は、de:code 2019で行われた「上級サポート エンジニアの経験お伝えします:Visual Studio 2019 でメモリー リークを追え!」というセッションのレポートをお届けします。
本セッションは以下のような方にオススメです。
不断の努力でアプリケーションを開発して、やっとの思いでリリースしたのに「コンピューターのメモリが不足しています」というメッセージに涙で枕を濡らしたことがある
ちなみにスピーカーの牧大介氏は、私自身、サポートでお世話になった方で、何度も何度も何度も窮地を助けて頂きました。毎度の「至急」にも快くご対応頂きましたm(_ _)mまた、このような形でお会いできるのが非常に光栄です。
このセッションのすごいところ
かなり実践に即した内容でした。本セッションの内容は、すぐに現場に適用可能といっても過言ではございません。スピーカーの牧大介氏がVisual Studioを起動して、その場でコーディング、メモリーリークを意図的に発生させて、プロファイルを取得して解析、問題のあるオブジェクトを発見するという、まるでリズミカルな音楽を奏でているような、もしくはスリリングな展開のドラマを見ているような臨場感の溢れるセッションでした(^o^)見ていて興奮しました。
メモリーリーク
アプリケーション開発者なら誰もが経験したことがあるであろう、あの忌まわしきOutOfMemoryのエラーメッセージと同時に、アプリケーションが落ちるアレです。一旦これを目にすると、休日も気になってしょうがなくて、心ここにあらず、食事も喉を通らないような状態になってしまいます。つらみー。
本セッションでは、その解決方法を非常に論理的にわかりやすくご説明されていました。
レガシーな対処方法
セッションは、(おそらく)サポート宛に来たよくある質問を例に挙げて、それ対する回答をスピーカーである牧大介氏が回答するという形で進みました。
今回の最も核となる質問は以下でした。
「レガシーな方法」と「イマドキな方法」を比較していました。まずはレガシーな方法から。
ダンプファイルを取得して解析するという手法です。コマンドは以下のとおりです。
procdump.exe -ma -a -r someapp.exe C:\temp\1.dump
ただしあくまでこのコマンドで取得できるのはダンプ情報であり、スナップショットです。そのダンプを取った時点での情報です。このように、たった一つのスナップショットだけでは、オブジェクトの傾向(どのオブジェクトが増えているかとか)を知ることは出来ません。
以下のように、procdumpコマンドで取得したダンプ情報をwindbgで解析している様子がセッションで公開されていました。
見るだけでうんざりしてしまうような感じですね、、、(´・ω・`) こんなのを解析してメモリリーク突き止めるなんて、徒手空拳、体当たりでの中央突破っていう感じです・・・(><)
イマドキな対処方法
先程ご紹介したメモリーダンプのスナップショット取得という方法ではなく、もっとインテリジェントでエレガントな方法がセッションの中で披露されていました。
それが、Visual Studioによるメモリ解析です。
この方法は端的に言ってしまうと以下でございます。
Visual Studioでデバッグ実行中のアプリケーションに対して、メモリのスナップショットを定点観測的に複数回取得し、その差分を観察することで、増加したオブジェクトを分析する。
イメージにしますと、こんな感じです。
なんかすごそうな雰囲気醸し出してますよね。オラワクワクすっぞ〜。
では、実際にやってみましょう。
牧大介さんのセッションのようにライブ感溢れるレポートをお届けします。
以下のようなコードを作成してみました。これは、本セッションで紹介されたコードです。Dictionary型の変数をForm1のインスタンス変数として持っていますので、ボタンをクリックするたびに100MBずつメモリが増加していきます。ちなみにGCするボタンがありますが、変数dは強参照になりますので、ガベコレでクリアされません。
public partial class Form1 : Form { Dictionary<string, byte[]> d = new Dictionary<string, byte[]>(); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { d.Add(Guid.NewGuid().ToString(), new byte[100 * 1024 * 1024]); } private void button2_Click(object sender, EventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }
デバッグ実行してみましょう。普段はすぐ閉じてしまう(><)診断ツールを表示しておきます。これがキモですので。
まずいちばんはじめのスナップショットを取得します。「スナップショットの作成」をクリックします。
スナップショットが作成されました。
ここで「メモリを増やす(´・ω・`)」ボタンをクリックします。
「診断ツール」のメモリが上がりました。
もう一回クリックしてみます。
さらに上がりました。
もう何回かやってみましょう。ポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチポチ。
あ、
OutOfMemoryで落ちてしまいました(><)やっちまったなぁ~。大変申し訳ございませんでした。もう一回気を取り直してやり直します。3回位にとどめておきます。そして診断ツールは以下のようになりました。
そしてスナップショットを取ります。「ヒープサイズ(相違)」の「+307,200.61KB」をクリックします。
すると、一回目のスナップショットと、2回目のスナップショットの差分が出てきます。「サイズの相違(バイト)」を見てみますと、明らかに怪しげなものがあります(¬_¬。)「GCTest.Form1」ってさっき、OutOfMemoryを引き起こすコードを書いたFormですよね。「GCTest.Form1」をクリックしてみます。
「参照された型」をクリックしてみます。
キタ━━━━(゚∀゚)━━━━!!
Dictionary<string, byte[]>って例のアレぢゃないですか。しかも300MBってちょうど100MB × メモリを増やす(´・ω・`)をクリックした回数(3回)ですよね。
途中、ポチポチしすぎて、アプリをOutOfMemoryで落とすという失態がアリましたが、まぁ、ライブ感は出てましたよね。これで犯人は突き止めました。
まとめ
すごいですね。もうくせになりそうです。三度の飯よりプロファイル、どんと来いOutOfMemory。では、快適なプロファイルライフを!!No Profiling,No Life!!ずんちゃずんちゃ。
そういえば、Javaでも似たようなアプローチがあります。こちらもあわせてご覧頂き、Facebook等でシェアをお願いしますm(_ _)m 皆様のいいね!やシェアが私の承認欲求を満たします。