前回、VBAを擬人化して、変数が記号表によって管理されているというところまで書いた。
※読んでない方はこちら
thom.hateblo.jp
さて、今回はオブジェクトがメモリ上でどう扱われるのかという話。
次のコードで説明しようと思う。
Dim c As Collection Set c = New Collection
オブジェクトはメモリにどのように存在するのか
前回の話では、値がメモリに入っていて、記号表によって変数名とアドレスを対応付けていると説明した。
オブジェクトの場合もこんな風になっているんだろうか。
実はこの図は間違いで、Integer型やLong型のように直接値が入っているわけではない。
オブジェクト型変数の場合、記号表からアドレスを参照した先には、更に別のメモリ領域を指すアドレスが入っていてそこにオブジェクトの実体が居る。
図中にスタックメモリ・ヒープメモリと書いたが、これはメモリの種類が違うわけではなく、同じメモリでスタック領域・ヒープ領域と区画が分けられているだけである。ちなみにメモリの領域としては他に実行中のプログラム自体が保存されるプログラム領域、グローバル変数などが保存される静的領域がある。
今回メモリ領域の違いについては詳しく説明しないが、普段ローカル変数で使ってるのがスタック領域、オブジェクトが保存されるのがヒープ領域ということ。
オブジェクト変数が宣言されてからオブジェクト(実体)がセットされるまでの流れ
さて、先ほどの図では変数cにCollectionが入った状態のメモリ図を見せたけれど、今回はコードを順に追いながら説明する。
まずDim c As Collectionが実行されると、インタープリターはスタックメモリに4バイト確保し、その中身にゼロをセットする。
オブジェクト型の変数は中身が参照先アドレスである。その参照先アドレスが0というのはつまり何も参照していない状態。これが実はVBAのNothingの正体である。
次にSet c = New Collectionが処理される。まず代入式の右辺にある「New Collection」により、ヒープ領域にCollectionの実体が作られる。
そして作成されたCollectionの実体を指すアドレスが、変数cが指すスタックメモリに書き込まれる。
以上がオブジェクト変数を宣言して新規オブジェクトを作成した際のメモリの動きである。
二つの変数に同じオブジェクトを参照させる
次に、もう一つCollection型の変数dを作成し、その変数dに変数cをセットしたケースを説明する。
Dim c As Collection Set c = New Collection Dim d As Collection Set d = c
とりあえずDim d As Collectionまで完了したのが次の図。
次に、Set d = cを処理する。
すると、変数dと変数cは同じオブジェクトを指すことになる。
次に、先ほどのコードに1行加えて、Set c = Nothingとしてみよう。
Dim c As Collection Set c = New Collection Dim d As Collection Set d = c Set c = Nothing
オブジェクトが破棄されるタイミング
Nothingをセットするとオブジェクトが破棄されると教えられた人も居るだろう。でもその理解は正しくない。
Set c = Nothingとすると変数cからオブジェクトへの参照が無くなるだけで、dからの参照は有効なままである。
オブジェクトが破棄されるのは、そのオブジェクトがどこからも参照されなくなった時である。
プロシージャが終了するとローカル変数はすべて破棄されるので、変数dも消滅する。するとdからオブジェクトへの参照がなくなり、Collectionはどこからも参照されていない状態となる。そして最終的にVBAによって片づけられる。
このようにオブジェクトへの参照が0になった時点で破棄される管理方式を、参照カウント方式と呼ぶ。
循環参照を作るとオブジェクトが破棄されなくなる。
参照カウント方式というのは一つ弱点があって、オブジェクト同士で循環参照を作ってしまうと参照カウントが0にならずにいつまでもメモリに残ってしまう。
コードを書いて実証してみよう。
まずは、循環参照ではないバージョンから。
Sub Sample() For i = 1 To 100000 Call 非循環参照 Next End Sub Sub 非循環参照() Dim c As Collection Set c = New Collection Dim d As Collection Set d = New Collection c.Add New Collection d.Add New Collection End Sub
cとdにそれぞれ新しいコレクションをセットする「非循環参照」マクロを、Sampleマクロから10万回呼んでみる。
実行前のメモリ使用量は約14メガバイト。
そしてマクロを実行してみるが、大して変わらない。
次に、循環参照させてみる。
Sub Sample() For i = 1 To 100000 Call 循環参照 Next End Sub Sub 循環参照() Dim c As Collection Set c = New Collection Dim d As Collection Set d = New Collection c.Add d d.Add c End Sub
このコードでは、コレクションcがdを保持し、コレクションdがcを保持することになる。
実行前のメモリ使用量は約14メガバイト。
実行後は、、なんと43メガバイト!
もう一度実行してみると、、73メガバイト!
とまあ、こんな感じで、循環参照させてると参照カウントが減らないということが分かった。
ちなみに、こんな風に最後にNothingを代入しても結果は同じである。
Sub 循環参照() Dim c As Collection Set c = New Collection Dim d As Collection Set d = New Collection c.Add d d.Add c Set c = Nothing Set d = Nothing End Sub
Nothingで消えるのは変数からの参照であって、オブジェクト同士に持たせた参照は消えない為だ。
循環参照させたオブジェクトを最後に破棄したい場合、必要なのは変数へのNothingではなくて、循環参照の解消である。
つまり、コレクションcにAddしたdをRemoveしてやれば良い。
Sub 循環参照() Dim c As Collection Set c = New Collection Dim d As Collection Set d = New Collection c.Add d d.Add c c.Remove 1 End Sub
すると、下図のようになり、
プログラム終了で変数c、変数dが破棄され、
どこからも参照されていない図の下のコレクションが破棄され、
参照を失ったもう一つのコレクションも破棄される。
これらの実験はExcelを終了させればメモリは解放されるので、そんなに危険はない。実際にやってみたい方はどうぞ。
なお、JavaとかC#とかVB.Netは参照カウント方式じゃなくてガベージコレクション(ごみ集め)という方式を採用している。
ガベージコレクション方式では、たとえヒープ領域内で循環参照していてもスタック領域から参照されていないオブジェクトは一定時間ごとに自動的に破棄してくれる。
巡回お掃除ロボが備わってるわけだ。うらやましい。
まとめと補足
オブジェクトはヒープメモリに存在し、オブジェクト変数の中身は値ではなくオブジェクトのアドレスが入っている。このような性質から、オブジェクト型は参照型に分類される。
オブジェクト変数にオブジェクト変数を代入するとアドレスのコピーになるので、結果的に参照先のオブジェクトは同じである。
何処からも参照されなくなったオブジェクトは結果的に破棄されるのであって、Nothingを代入することで直接オブジェクトが破棄されるわけではない。
オブジェクトの参照を保持できるのは変数の他に、With文やCollectionや配列などがある。
【参考】変数を作らずにWith文に直接新規オブジェクトを保持させることもできる。
thom.hateblo.jp
この場合、End Withで参照が破棄されるので、オブジェクトを生かすにはコレクションや別の変数に参照を保持させると良い。
補足として、今回ヒープ領域にCollectionを8バイトとして格納した図を用いたが、あくまでサンプルなので8バイトというのはデタラメである。実際にはCollectionオブジェクトのItemプロパティは更に先頭アイテムのアドレスを保持しており、それぞれのアイテムはヒープ上にバラバラに存在していると思われる。
また、AddやRemoveなどのメソッドは各オブジェクト内に存在するわけではなく、全Collectionオブジェクトで共通である。以下の書籍によるとオブジェクトのメソッドはメソッドエリア(静的領域)に存在し、オブジェクトを量産してもメソッドが占めるメモリ領域は一か所のみとなるそうだ。

- 作者: 平澤章
- 出版社/メーカー: 日経BP社
- 発売日: 2011/04/07
- メディア: 単行本
- 購入: 6人 クリック: 92回
- この商品を含むブログ (20件) を見る
ただし、実際にVBAでどのようにメモリ上にオブジェクトが展開されるかは不明。
今回は解説があまり複雑にならないよう、オブジェクトはヒープ領域に存在すると書いたが、厳密にはオブジェクトからさらにメソッドへの参照やアイテムへの参照が張り巡らされているイメージ。