.NETでガベージコレクションによって再現性が低いバグになるケース

この記事は公開から3年以上経過しています。

.NETのようにメモリ管理された開発言語では、正しい変数のライフサイクル管理を行わないとメモリリークや再現性が低いバグを作り込む可能性が高くなる…という例を簡単なサンプルを用いて紹介します。

サンプルソースコード(C#/WinForms)

Form1
file
フォーム(Form1)に画面上にラベル(label1)とボタン(button1)を配置しているだけです。

Form1.cs

using System;
using System.Windows.Forms;

namespace OptimizationTest210403
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var i = 0; // カウンタ
            // 1秒間隔の定周期タイマーを起動
            var tm = new System.Threading.Timer((p) =>
            {
                // UIスレッドにディスパッチして画面更新
                this.BeginInvoke(new EventHandler((o2, e2) => label1.Text = $"{i++}"));
            }, null, 0, 1000);

            // ボタン押下で全世代の強制ガベージコレクションを実行
            button1.Click += (o2, e2) => GC.Collect();
        }
    }
}

説明

サンプルプログラムを起動するとフォーム中央のLabel1に1秒間隔の定周期タイマーによって更新されるカウント値を表示しますが、フォーム中央のGCボタンを押下すると定周期タイマーオブジェクトがGCで回収されてしまうことによりカウントアップが停止します。

このサンプルでは

  • タイマーオブジェクトを意図的にローカル変数(スタック)に保持。
  • タイマーオブジェクトを異なるスコープの変数に保持したり参照しない(強参照を持たせない)。
  • GCタイミングを手動で制御。

によって利用中のオブジェクトが解放されてしまう問題を再現していますが、実際の開発ではプログラムが複雑化することにより強参照で変数(メモリー)のライフサイクルがあいまいであったり、GCが行われるタイミングも不定になることから、難解なバグの原因になることが容易に想像できるのではないかと思います。

ちなみに、.NET開発におけるGCを含めたメモリー管理の理解を向上させて品質やパフォーマンスを改善したい、極めたいという方には特に「C#プログラマのための.NETアプリケーション最適化技法」の

第3章 型の内部
 ・例
 ・参照型と値型の違い
 ・ストレージ、割り当て、解放
 ・参照型の内部
 ・値型の内部
 ・値型を使用する場合のベストプラクティス
第4章 ガベージコレクション
 ・ガベージコレクションを使用する理由
 ・トレースによるガベージコレクション
 ・ガベージコレクションの種類
 ・ジェネレーション
 ・GCのセグメントと仮想メモリ
 ・終了処理
 ・弱参照
 ・ガベージコレクターの操作
 ・ガベージコレクションのベストプラクティス

辺りは目からウロコの必読です。

参考ウェブサイトなど

以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする