FlyweightパターンとMemoizationとメモリリーク

デザインパターンの1つFlyweightパターンは要するにMemoization (メモ化) のことです。Flyweightパターンというと何のことか良くわからないので、Memoizationパターンと読んだ方が適当なように思います。

メモ化とは、関数が返した値を覚えておいて、再度同じ関数が呼ばれた時に、覚えておいた値を返すことで同じ計算を何度も行わずに済ませることです。

メモ化を関数に適用した場合は値を返しますが、オブジェクト指向の場合はオブジェクトの参照を返します。そのため、結果として返されたオブジェクトは、複数のオブジェクトから共有されることになります。オブジェクトの生成のコストを削減することができます。

共有されたオブジェクトが書き換えられることは不都合です。どのオブジェクト間でメモ化されたオブジェクトが共有されるのかをコントロールすることは難しいからです。一般に、メモ化するようなオブジェクトはImmutableとしておくことが多い。Immutableであれば、書き換えられるおそれがないので、オブジェクトが共有されていても問題にならないからです。

返り値を覚えておくメモはstaticであることが多く、メモがオブジェクトである場合はSingletonを用いる。staticは嫌われるけれども、場合によってはstaticの方が効率的なのです。基本はdynamic?でつくって、最適化を施したいところをstaticにするというのが正しい戦略でしょう。

よく言われるように早すぎる最適化は良くないので、特に何も問題が生じていないのにメモ化を行うことはしてはならない。システムとして動くようになって、テストも存在した状態で、遅そうなところが特定のクラスのインスタンスの生成であるという仮説が立った時に初めてメモ化なりをしよう。無意味に最適化されたコードは、書くときは楽しいけれど、あとから後悔する。

例えば、シングルトンにメモされているオブジェクトへの参照が、増え続けるならばそれはメモリリークである。ガベージコレクタによって我々はメモリ管理から開放されたと思ったのに、メモ化という凝ったことをすると再びメモリリークの危険性に怯えなければならなくなる。

解決策としては、弱参照かソフト参照を利用することが考えられる。Javaの参照には

  • 強参照(普通の参照)
  • ソフト参照
  • 弱参照

という3種類がある。*1メモを強参照で持ったら、GCの対象にならない。しかし、ソフト参照か弱参照でオブジェクトをメモしていたら、そのオブジェクトを強参照しているオブジェクト(つまり使用中のオブジェクト)がいなければ、メモはGCの回収対象になる。

ソフト参照の方が弱参照より参照具合が強くて、ソフト参照はGCが動いても必ず回収されるとは限らない。ソフト参照は、メモリが非常に足りないときには回収されて、回収されるかどうかの判断はGC任せとなる。オブジェクトの生成が重いという場合はソフト参照を使うのがよいだろう。

弱参照はGCが動いたら回収される。オブジェクトを共有してメモリを節約したいという目的ならば弱参照を使うのがよいと思われる。

どのように実装するかはこんど考えてみることにする。

参考

*1:他にファントム参照ってのがあるらしいが、よう分からん