.NET の「実行時型情報」は immutable かつ GC 不可能

昨日は,開いた型である generics 型を使用して,実行時に新しい型を生成する方法について示しました*1
今日は,この点についてもう少し掘り下げてみてみましょう.



現在の CLR の実装では,全ての参照型オブジェクトは,その型に対応する TypeHandle を保持しています*2.このハンドルの値はアプリケーションドメイン内で一意であり,コンパイル時ではなく実行時に決定されます.
一度も使用されていない型は型情報と型ハンドルが存在しない一方で,一度生成された型情報と型ハンドルを削除するには,アプリケーションドメイン全体をアンロードする必要があります.
そのため「型」を無制限に作成・使用するアプリケーションは,解放されないリソースを抱え込んでしまうことになります.
このような制限があるため,例えば動的型付け言語で型が変更されるたびに新しい CLR の型を発行してしまうと,回収されない型情報の氾濫が問題となるかもしれません.
IronPython をはじめとする多くの .NET スクリプト言語では,自身の実行エンジンの中に独自の型システムを構築し,CLR の型システムを直接は利用しないという戦略を採っていますが,その背景事情の1つがこの CLR の型システムの制約だと考えられます.
昨日示した通り,Type.MakeGenericType メソッドは型から型を作り出す方法の1つです.
Type クラスのインスタンスメソッドを見てみると,似たようなメソッドがいくつか並んでいることに気付くでしょう.

これらのメソッドを組み合わせたり再帰的に実行したりすることで,既存の型から大量の型を生成することが可能です.
手元の環境で試してみたところ,型が生成されるたびに Process Explorer に表示されるプロセスの「Virtual Size」が増え続け,GC を発生させても単調増加を続けました.約 40 万の型を生成したところで,1 GB のメモリがコミットされており,当然のことながらシステムのページファイル使用量は 1 GB を越える大きな値になっていました*3
近年は .NET の世界でも AOP による実行時ウィービングや,実行時コード生成による動的なプログラミングが注目されています.これらの技法では実行時に実装型を合成するという手法がしばしば用いられます.一般的なプロセス(より正確にはアプリケーションドメイン)の寿命では,このような動的生成された型の情報が問題になることはないでしょう.しかし,非常に長期間稼働し続ける環境では,このように CLR の型情報に関する制限があることを頭の片隅にとどめておくと良いかもしれません.

*1:id:NyaRuRu:20060731#p1

*2:『Essential .NET』4.1章「実行時における型」

*3:この 1GB = 約40万個という関係は,型のネスト数や名前の長さによって変化するかもしれません.