C# Programming

ImageMicrosoft .NET Framework のガベージコレクション

開発環境: Visual Studio 2003

目次

  1. 目次
  2. 目的
  3. 参考書
  4. GCお任せパターン? Dispose パターン? using パターン?どれをつかえばいいの?
  5. 実験で確かめてみる
  6. .NET Framework のメモリ管理の内部動作
  7. クラスを作成する場合の注意
  8. ソースコード
  9. 改定記録

目的

GotDotNetで、激論になりましたが、どうも自分でも納得できないところがありました。
というのは、明示的なDispose()、あるいはusingステートメントパターンが良いと言う意見がありますが、MSDNのサンプルの多くは、暗黙のGCで書かれているサンプルが多いですよね。

もし、MSがDispose(), usingステートメントをお勧めなら、MSDNのサンプルはDispose()パターン、あるいはusingステートメントパターンでかかれると思うんですよね。

それで、はたしてDispose(), usingステートメントを使うべきなのか、あるいはGCにおまかせパターンでよいのか、GDI+ の場合について検証してみました。

以下は、メモリ管理に関して、ある程度の知識があることを前提としています。

参考書

(1) ガベージコレクション入門: Microsoft .NET Framework の自動メモリ管理 Part I
(2) ガベージコレクション入門: Microsoft .NET Framework の自動メモリ管理 Part II
(3) MSDN: System.GC
(4) GotDotNet: IDisposable/Dispose/using に関して
(5) .NET関連資料室  青柳さんの−usingを使え、使えったら使え(^^)−

GCお任せパターン? Dispose パターン? using パターン?
どれをつかえばいいの?

メモリ管理パターンは次の3つ (GCお任せパターン、Dispose パターン、using 文パターン) が考えられます。
Microsoft のサンプルプログラムでは、ほとんどのものが GCお任せパターンです。
一方で、Disposeを実装が実装されている場合は、Disposeを呼びなさいと言うことも書いてあります。
いったい、どうすればいいのでしょうか。

実験の結果、次のような結論になりました。

パターン コードパターン メリット デメリット
GCお任せ Graphics g = xyz;
Pen rPen = new Pen(...);
g.DrawLine(...);
メモリを意識しないので、直感的で簡単。 GCで回収されるまで、リソースが使われる。

特に大きな画像などのリソースを消費する場合には、問題となる。

Dispose

(Dispose が実装されている場合のみ)

Graphics g = xyz;
Pen rPen = new Pen(...);
g.DrawLine(...);
rPen.Dispose();
g.Dispose();
明示的にリソースを開放できる。 Disposeが実装されているかどうかを確認して、コーディングしなければならないので、面倒。

メモリを意識しないといけない。

using 文 using (Graphics g = xyz)
using (Pen rPen = new Pen(...)
{
g.DrawLine(...);
}
明示的にリソースを開放できる。

コンパイラが、try-finallyパターンでアセンブリを生成し、finally の中でDisposeが呼ばれるため、明示的にDispose 文を書く必要が無く、スコープもはっきりしているので、Disposeに比べてきれい。

try-finally-Disposeと呼び出される分、わずかに遅くなる。

 

using 文パターンを使用すべき場合
  1. 大きなリソース(画像、ファイル、データベース)を使用する場合。
  2. 常駐型、サービス。
  3. システムのメモリが十分でないような環境で使用する場合。
  4. 業務システム。

 

GCお任せパターンでもよい場合
  1. 比較的すぐに終了するアプリ。
  2. 大きなリソース(画像、ファイル、データベース)を使用しない場合。
  3. メモリをたくさん持っている人のお遊び用アプリ用。:-)

 

Disposeは?
usingではなく、Dispose を明示的に使うメリットはなさそう。

 

明示的にGCを呼ぶ方法もあり
メモリを使用している範囲が限定できて、上限がはっきりしている場合には、GCお任せパターンで記述しておいて、明示的に System.GC.Collect()を呼び出して、強制的にGCを使うパターンもありだと思います。

このほうが、ちまちまとusing ごとにメモリを開放するよりは、メモリのリソースを目いっぱい使用しておいて、一気にページ単位で不要になったメモリを回収できるので、わずかながら性能がよさそうです。

 

実験で確かめてみる

OnPaint イベントで、GDI+で線を描画するガベージコレクションテストプログラムを作成しました。
次のテスト用アプリケーションは、指定回数分、直線、文字、画像を指定回数描画し、その時間を測定するプログラムです。

Memory のラジオボタンは、それぞれ次のとおりです。

  • Auto GC → GCお任せ
  • Dispose → Disposeパターン
  • Using → using文パターン

Use Imageをチェックした場合だけ、画像も処理します。

Image

1.27MBの画像を使用した場合、それぞれ3つのパターンで実行した場合のパフォーマンスモニターの結果を次に示します。
各パターン実行前には、System.GC.Collect() により、強制的にGCを実行しています。

Image

これより、次のことがわかります。

  1. 速度的には、どの方法でもほとんど同じである。
  2. GCお任せの場合は、かなりメモリを消費する。

次に、大きなリソースである画像の影響がどのぐらいあるのか確認するために、画像を扱う場合と扱わない場合の違いを測定したのが次の図です。

Image

このときのPCは、すべてのプロセスがオンメモリで動作可能な状況でしたので、ガベージコレクション自体はほとんど時間はかかってないようです。

MSDNより抜粋
『Microsoftの処理性能テストによれば、管理ヒープ内のメモリ確保は、Win32 HeapAllocファンクションによる標準のメモリ確保よりも高速である。また、200MHz Pentiumでジェネレーション0に完全にGCをかけても1ミリ秒以下の時間しかかからないと示されている。Microsoftの目標は、GCに通常のページフォルトよりも長い時間がかからないようにすることである。 』

とあるように、GCそのものは思った以上に高速でした。

画像を使用しない場合は、プロセスの最大メモリ使用量が約17MBで収まったのに対して、画像を使用する場合は最大で約180MBにも達しています。このとき 、搭載メモリ1GBで、空きメモリエリアが約500MBのPCでテストしましたので、空きメモリの約 1/3 までメモリを食いつぶされたことになります。

GCお任せではガベージコレクションが実行されるまでリソースが開放されないため、特に画像のような大量のリソースを消費する場合には、かなりメモリが消費されます。このため、ガベージコレクションで遅くなるというよりは、ページングが起きて性能低下する危険性が高 まります。特にPCのメモリが少ない場合はスラッシングに陥る危険性が高いので、注意が必要です。


ジェネレーションごとのヒープサイズ

サイズはガベージ コレクタによって調整され、アプリケーションの実行中に変動します。ヒープサイズの大きさをパフォーマンスモニターで観察してみると、かなり頻繁にヒープサイズが自動調整されてい ることがわかります。おそらくシステム環境を見ながら、最適な値に調整しているのだと思います。

用語 意味
GC ヒープサイズ 各GCのジェネレーションに割り当てられる最大のバイト数を表示します。このサイズを超えるとGCが発生します。

 

GC ヒープサイズ 変動範囲例
Gen 0 heap size 170KB-2400KB の範囲で変動
Gen 1 heap size 12B-320KB の範囲で変動
Gen 2 heap size 12B-760KB の範囲で変動

.NET Framework のメモリ管理の内部動作

 

用語 意味
.NET 管理ヒープ CLR(Common Language Runtime)は、すべてのリソースを .NET 管理ヒープから確保する。プロセスが初期化される時、CLRはアドレス空間内の連続領域を予約する。初期状態ではこの領域には物理メモリはいっさい割り当てられていない。このアドレス空間内領域が管理ヒープである。
ルーツ ルーツは記憶位置を識別するもので、管理ヒープ上のオブジェクトか、nullをセットされたオブジェクトのどちらかを参照する。すべてのアプリケーションは、ルーツのセットを持っている。アクティブなルーツのリストは、JIT (just-in-time) コンパイラとCLRによって管理される。

.NETでは、引数、静的変数、ローカル変数などのすべてのリソースは .NET 管理ヒープから確保される。これらのリソースは、ルーツから参照される。ルーツは、CLRによって管理される。

.NET Framework では、Generation 0(新), 1, 2(古) の3世代のメモリ管理を行います。
すべてのオブジェクトはGeneration 0 として生成されます。

通常、ガベージコレクションには、かなりの負荷がかかります。一昔前のシステムだと、一度GCがかかるとその瞬間システムの反応が止まる、あるいは極端に遅くなるのがわかるほどです。このため、GCの効率化は前からの課題でした。通常、古いオブジェクトほど長い時間生き残る可能性があり、新しいオブジェクトほど短命である可能性が高いので、新しいオブジェクトは頻繁に、古いオブジェクトはそれほど頻繁ではなくGCすることにより、高速なGCを行うことができます。

また、ファイナライザーを持つオブジェクトと、持たないオブジェクトでは処理が違ってきます。
ファイナライザーを持つオブジェクトが.NET管理ヒープにアロケートされた場合、ファイナライゼーションキューに追加され、F Reachableキューを経由して開放されます。
このあたりの動きについて、以下の図で説明していきます。

下図では、オブジェクトA-Fがアロケートされ、その後B, Eが参照されなくなった時のルーツ、.NET管理ヒープ、Finalizationキュー、F Reachableキューの様子を示す。

Image

この状態で、管理ヒープのG0の使用量が規定値を超えた場合に、ガベージコレクションが実行される。オブジェクトはG1世代となる。Object Bは、ファイナライザーを持たないため、すぐに開放される。一方、Object Eは、ルーツから参照されないため、F Reachable キューに移動される。F Reachable キューに登録された時点で、Finalize呼び出しのためのスレッドが動き出し、Object Eのファイナライザーを起動する。

Image

Object Eのファイナライザーを実行完了すると、F Reachable キューからObject Eが削除される。この状態で、管理ヒープのObject Eはどこからも参照されない状態になり、次回のGCで削除できる状態になる。

Image

次に、新しいObject G, H がアロケートされた場合の状態を下図に示す。

Image

下図は、そこから、D, Gの参照が外れた状態を示す。

Image

下図は、2回目のGCがかかったときの状態を示す。参照されていないD, E, Gが回収される。

Image

下図は、さらにオブジェクト I が追加されたときの状態を示す。

Image

 

クラスを作成する場合の注意

Finalizeメソッドは限り使わないほうがよい。
Finalizeメソッドを持つオブジェクトは、より古いジェネレーションに昇格される。また、このオブジェクトが直接、間接的に参照しているオブジェクトも、すべて昇格される。 その分、GCが1回遅れ、メモリがタイトになる。

10000個のオブジェクトの配列があれば、そのなかのオブジェクト1つ1つについてFinalizeメソッドを呼び出される。

Finalizeメソッドがいつ実行されるかは、GC次第なので、リソースを抱えたままになる場合もある。

C#の場合は、Finalizeメソッドはオーバライドできない。デストラクタを使用する。
C#の場合は、Finalizeメソッドはオーバーライドできません。コンパイルエラーになります。そのかわり、デストラクタを使用します。
IDisposable.Dispose メソッド
ファイル、ストリーム、ハンドルなどのアンマネージ リソースを閉じたり解放したりする場合に実装しなければならない。

Dispose メソッドは明示的に呼び出す必要があるため、 IDisposable を実装するオブジェクトは、 Dispose が呼び出されなかった場合にリソースの解放を処理するファイナライザも実装する必要があります。

改定記録


日付コメント
2004/10/17全面改訂
2004/5/23全体デザイン再構成
2002/3/2初版作成

 

ソースコード

ガベージコレクション テスト用アプリケーション
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace WinForm1
{
	/// 
	/// Form1 の概要の説明です。
	/// 
	public class Form1 : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Button button1;
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.Button button3;
		private System.Windows.Forms.Button button4;
		private System.Windows.Forms.Button button5;
		private System.Windows.Forms.Button button6;
		private System.Windows.Forms.Label label2;
		private System.Windows.Forms.NumericUpDown numericUpDown1;
		private System.Windows.Forms.Label label3;
		private System.Windows.Forms.RadioButton rbNone;
		private System.Windows.Forms.RadioButton rbDispose;
		private System.Windows.Forms.RadioButton rbUsing;
		private System.Windows.Forms.GroupBox groupBox1;
		private System.Windows.Forms.Button button2;
		private System.Windows.Forms.OpenFileDialog openFileDialog1;
		private System.Windows.Forms.CheckBox checkBoxUseImage;
		private System.Windows.Forms.Label labelCount;
		/// 
		/// 必要なデザイナ変数です。
		/// 
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			//
			// Windows フォーム デザイナ サポートに必要です。
			//
			InitializeComponent();

			//
			// TODO: InitializeComponent 呼び出しの後に、
			// コンストラクタ コードを追加してください。
			//
		}

		/// 
		/// 使用されているリソースに後処理を実行します。
		/// 
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows フォーム デザイナで生成されたコード 
		/// 
		/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
		/// コード エディタで変更しないでください。
		/// 
		private void InitializeComponent()
		{
			this.button1 = new System.Windows.Forms.Button();
			this.label1 = new System.Windows.Forms.Label();
			this.button3 = new System.Windows.Forms.Button();
			this.button4 = new System.Windows.Forms.Button();
			this.button5 = new System.Windows.Forms.Button();
			this.button6 = new System.Windows.Forms.Button();
			this.label2 = new System.Windows.Forms.Label();
			this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
			this.label3 = new System.Windows.Forms.Label();
			this.rbNone = new System.Windows.Forms.RadioButton();
			this.rbDispose = new System.Windows.Forms.RadioButton();
			this.rbUsing = new System.Windows.Forms.RadioButton();
			this.groupBox1 = new System.Windows.Forms.GroupBox();
			this.button2 = new System.Windows.Forms.Button();
			this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
			this.checkBoxUseImage = new System.Windows.Forms.CheckBox();
			this.labelCount = new System.Windows.Forms.Label();
			((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
			this.groupBox1.SuspendLayout();
			this.SuspendLayout();
			// 
			// button1
			// 
			this.button1.Location = new System.Drawing.Point(296, 128);
			this.button1.Name = "button1";
			this.button1.Size = new System.Drawing.Size(72, 24);
			this.button1.TabIndex = 0;
			this.button1.Text = "Run";
			this.button1.Click += new System.EventHandler(this.button1_Click);
			// 
			// label1
			// 
			this.label1.Font = new System.Drawing.Font("MS UI Gothic", 
				18F, System.Drawing.FontStyle.Regular, 
				System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
			this.label1.Location = new System.Drawing.Point(176, 168);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(112, 32);
			this.label1.TabIndex = 2;
			this.label1.Text = "0.0";
			this.label1.TextAlign = System.Drawing.ContentAlignment.TopRight;
			// 
			// button3
			// 
			this.button3.Location = new System.Drawing.Point(40, 208);
			this.button3.Name = "button3";
			this.button3.Size = new System.Drawing.Size(64, 24);
			this.button3.TabIndex = 3;
			this.button3.Text = "Do GC 0";
			this.button3.Click += new System.EventHandler(this.button3_Click);
			// 
			// button4
			// 
			this.button4.Location = new System.Drawing.Point(120, 208);
			this.button4.Name = "button4";
			this.button4.Size = new System.Drawing.Size(64, 24);
			this.button4.TabIndex = 4;
			this.button4.Text = "Do GC 1";
			this.button4.Click += new System.EventHandler(this.button4_Click);
			// 
			// button5
			// 
			this.button5.Location = new System.Drawing.Point(208, 208);
			this.button5.Name = "button5";
			this.button5.Size = new System.Drawing.Size(64, 24);
			this.button5.TabIndex = 5;
			this.button5.Text = "Do GC 2";
			this.button5.Click += new System.EventHandler(this.button5_Click);
			// 
			// button6
			// 
			this.button6.Location = new System.Drawing.Point(304, 208);
			this.button6.Name = "button6";
			this.button6.Size = new System.Drawing.Size(64, 24);
			this.button6.TabIndex = 6;
			this.button6.Text = "Do GC All";
			this.button6.Click += new System.EventHandler(this.button6_Click);
			// 
			// label2
			// 
			this.label2.Font = new System.Drawing.Font("MS UI Gothic", 
				18F, System.Drawing.FontStyle.Regular, 
				System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
			this.label2.Location = new System.Drawing.Point(304, 168);
			this.label2.Name = "label2";
			this.label2.Size = new System.Drawing.Size(64, 24);
			this.label2.TabIndex = 7;
			this.label2.Text = "SEC";
			// 
			// numericUpDown1
			// 
			this.numericUpDown1.Font = new System.Drawing.Font("MS UI Gothic", 
				15.75F, System.Drawing.FontStyle.Regular, 
				System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
			this.numericUpDown1.Location = new System.Drawing.Point(288, 16);
			this.numericUpDown1.Maximum = new System.Decimal(new int[] {
				1000000,
				0,
				0,
				0});
			this.numericUpDown1.Name = "numericUpDown1";
			this.numericUpDown1.Size = new System.Drawing.Size(88, 28);
			this.numericUpDown1.TabIndex = 8;
			this.numericUpDown1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
			this.numericUpDown1.Value = new System.Decimal(new int[] {
						500,0,0,0});
			// 
			// label3
			// 
			this.label3.Font = new System.Drawing.Font("MS UI Gothic", 
				15.75F, System.Drawing.FontStyle.Regular, 
				System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
			this.label3.Location = new System.Drawing.Point(216, 16);
			this.label3.Name = "label3";
			this.label3.Size = new System.Drawing.Size(72, 24);
			this.label3.TabIndex = 9;
			this.label3.Text = "Repeat";
			// 
			// rbNone
			// 
			this.rbNone.Checked = true;
			this.rbNone.Location = new System.Drawing.Point(16, 24);
			this.rbNone.Name = "rbNone";
			this.rbNone.Size = new System.Drawing.Size(80, 32);
			this.rbNone.TabIndex = 10;
			this.rbNone.TabStop = true;
			this.rbNone.Text = "Auto GC";
			// 
			// rbDispose
			// 
			this.rbDispose.Location = new System.Drawing.Point(16, 48);
			this.rbDispose.Name = "rbDispose";
			this.rbDispose.Size = new System.Drawing.Size(80, 32);
			this.rbDispose.TabIndex = 11;
			this.rbDispose.Text = "Dispose";
			// 
			// rbUsing
			// 
			this.rbUsing.Location = new System.Drawing.Point(16, 72);
			this.rbUsing.Name = "rbUsing";
			this.rbUsing.Size = new System.Drawing.Size(80, 32);
			this.rbUsing.TabIndex = 12;
			this.rbUsing.Text = "Using";
			// 
			// groupBox1
			// 
			this.groupBox1.Controls.Add(this.rbNone);
			this.groupBox1.Controls.Add(this.rbDispose);
			this.groupBox1.Controls.Add(this.rbUsing);
			this.groupBox1.Location = new System.Drawing.Point(176, 48);
			this.groupBox1.Name = "groupBox1";
			this.groupBox1.Size = new System.Drawing.Size(104, 112);
			this.groupBox1.TabIndex = 13;
			this.groupBox1.TabStop = false;
			this.groupBox1.Text = "Memory";
			// 
			// button2
			// 
			this.button2.Location = new System.Drawing.Point(296, 88);
			this.button2.Name = "button2";
			this.button2.Size = new System.Drawing.Size(72, 24);
			this.button2.TabIndex = 14;
			this.button2.Text = "Set Image";
			this.button2.Click += new System.EventHandler(this.button2_Click);
			// 
			// checkBoxUseImage
			// 
			this.checkBoxUseImage.Location = new System.Drawing.Point(296, 64);
			this.checkBoxUseImage.Name = "checkBoxUseImage";
			this.checkBoxUseImage.Size = new System.Drawing.Size(80, 16);
			this.checkBoxUseImage.TabIndex = 15;
			this.checkBoxUseImage.Text = "Use Image";
			// 
			// labelCount
			// 
			this.labelCount.Font = new System.Drawing.Font("MS UI Gothic", 
				18F, System.Drawing.FontStyle.Regular, 
				System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
			this.labelCount.Location = new System.Drawing.Point(32, 168);
			this.labelCount.Name = "labelCount";
			this.labelCount.Size = new System.Drawing.Size(112, 32);
			this.labelCount.TabIndex = 16;
			this.labelCount.Text = "0.0";
			this.labelCount.TextAlign = System.Drawing.ContentAlignment.TopRight;
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
			this.ClientSize = new System.Drawing.Size(392, 246);
			this.Controls.Add(this.labelCount);
			this.Controls.Add(this.checkBoxUseImage);
			this.Controls.Add(this.button2);
			this.Controls.Add(this.groupBox1);
			this.Controls.Add(this.label3);
			this.Controls.Add(this.numericUpDown1);
			this.Controls.Add(this.label2);
			this.Controls.Add(this.button6);
			this.Controls.Add(this.button5);
			this.Controls.Add(this.button4);
			this.Controls.Add(this.button3);
			this.Controls.Add(this.label1);
			this.Controls.Add(this.button1);
			this.Name = "Form1";
			this.Text = "Garbage Collection Test";
			this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
			((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
			this.groupBox1.ResumeLayout(false);
			this.ResumeLayout(false);

		}
		#endregion

		/// 
		/// アプリケーションのメイン エントリ ポイントです。
		/// 
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}

		private void button1_Click(object sender, System.EventArgs e)
		{
			DateTime start = DateTime.Now;
			for(int i = 0; i < this.numericUpDown1.Value; i++)
			{
				this.Refresh();
				this.labelCount.Text = i.ToString();
			}
			DateTime end = DateTime.Now;
			TimeSpan diff = end - start;
			this.label1.Text = diff.TotalSeconds.ToString("f3");
		}
		
		private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
			if (this.rbNone.Checked)
			{
				Graphics g = e.Graphics;
				
				if (this.checkBoxUseImage.Checked)
				{
					Bitmap bmp = new Bitmap(fname);
					g.DrawImage(bmp, 0,0, 100, 100);
				}

				Pen rPen = new Pen(Color.Red, 1.0F);
				g.DrawLine(rPen, 0, 0, 100, 100);		
		
				SolidBrush brush = new SolidBrush(SystemColors.WindowText);
				Font font = new Font("Courier", 12);
				g.DrawString("文字列", font, brush, 50, 50);

			}
			else if (this.rbDispose.Checked)
			{
				Graphics g = e.Graphics;
				
				if (this.checkBoxUseImage.Checked)
				{
					Bitmap bmp = new Bitmap(fname);
					g.DrawImage(bmp, 0,0, 100, 100);
					bmp.Dispose();
				}

				Pen rPen = new Pen(Color.Red, 1.0F);
				g.DrawLine(rPen, 0, 0, 100, 100);	
				rPen.Dispose();
		
				SolidBrush brush = new SolidBrush(SystemColors.WindowText);
				Font font = new Font("Courier", 12);
				g.DrawString("文字列", font, brush, 50, 50);
				font.Dispose();
				
				g.Dispose();
			}
			else if (this.rbUsing.Checked)
			{
				using (Graphics g = e.Graphics)			
				{
					if (this.checkBoxUseImage.Checked)
					{
						using (Bitmap bmp = new Bitmap(fname))
							g.DrawImage(bmp, 0,0, 100, 100);
					}
					using (SolidBrush brush = 
						new SolidBrush(SystemColors.WindowText))
					using (Font font = new Font("Courier", 12))
					using (Pen rPen = new Pen(Color.Red, 1.0F))
					{
						g.DrawLine(rPen, 0, 0, 100, 100);
						g.DrawString("文字列", font, brush, 50, 50);
					}
				}
			}
		}

		private void button3_Click(object sender, System.EventArgs e)
		{
			GC.Collect(0);
		}

		private void button4_Click(object sender, System.EventArgs e)
		{
			GC.Collect(1);
		}

		private void button5_Click(object sender, System.EventArgs e)
		{
			GC.Collect(2);		
		}

		private void button6_Click(object sender, System.EventArgs e)
		{
			GC.Collect();		
		}

		private string fname = null;
		private void button2_Click(object sender, System.EventArgs e)
		{
			DialogResult res = this.openFileDialog1.ShowDialog();
			if (res == DialogResult.OK) 
			{
				fname = this.openFileDialog1.FileName;	
				this.checkBoxUseImage.Checked = true;
				Invalidate();
			}
		}

	}
}