Bitmap を SoftReference で管理すべきではない

追記:3.0 より Bitmap はネイティブヒープを利用しなくなりました。キャッシュは 3.1 以降なら LruCache を使えば大体問題ないと思います。

手が痛いので、簡単に。

一年以上前の記事ですが、CacheオブジェクトにはSoftReferenceをとか、最近だと、WeakHashMap なるものがあるのねなどのページで、Bitmap を SoftReference で管理しよう!みたいなのが紹介されていますが、

それなりにヒープを消耗する場合は Bitmap の開放を GC に任せてはいけません。

そんなわけですから、面倒ですが recycle メソッドをちゃんと呼んであげましょう。ドキュメントに "normally need not be called" とありますが、これは「ヒープによほど余裕があるなら呼ぶ必要はない」と読み替えてください。実際そうです。

そもそも Bitmap とは、ネイティブヒープ上に確保されたバッファを保持するクラスなわけですが、このバッファは GC が直接管理しているわけではありません。GC が管理しているのは、あくまで Bitmap オブジェクトです。GC さんが頑張ってお掃除しても、ファイナライザが呼ばれるまでは、ネイティブヒープに余裕はできません。これは、ネイティブヒープが逼迫し GC が行われても、すぐにはネイティブヒープは回復しない、ということを意味しています。割合残念な事実ですね。

SoftReference を使うと、問題は更に深刻化します。GC に回収されるタイミングが、通常の strong reference よりも遅くなってしまいます。勿論そのために SoftReference は存在するわけですが、GC に回収されるタイミングが遅くなれば、当然ファイナライザが呼び出されるタイミングも遅くなります。結局のところ、手遅れ(OutOfMemory)になりやすくなります。*1
どうしても SoftReference によるキャッシュ機構を利用したいなら、SoftReference で直接 Bitmap を管理するのではなく、Bitmap の所有権をあらわすオブジェクトを SoftReference で管理し、更にそのオブジェクトが ReferenceQueue に積まれるのを監視し、積まれたタイミングですぐに recycle メソッドを呼んでやるようにしましょう。こうすれば、手遅れになる可能性は比較的低くなります。勿論、手遅れになってしまうこともあります。SoftReference 周りの実装をよく分かった上でなら、意味があるかもしれません。そうでないなら、苦労の割りにあまり効果がなくても、文句は言えませんね。はい、言いません…

…それでも、OutOfMemory もちゃんとリカバーして待っていれば、いつかはネイティブヒープに余裕ができるのではないか、皆さんはきっとそうお考えでしょう。

甘い…ッ!甘すぎる……ッ!!

     ____________
    ヾミ || || || || || || || ,l,,l,,l 川〃彡|
     V~~''-山┴''''""~   ヾニニ彡|       finalize する・・・・・・!
     / 二ー—''二      ヾニニ┤       finalize するが・・・
    <'-.,   ̄ ̄     _,,,..-‐、 〉ニニ|       その時の
   /"''-ニ,‐l   l`__ニ-‐'''""` /ニ二|       指定まではしていない
   | ===、!  `=====、  l =lべ=|
.   | `ー゚‐'/   `ー‐゚&#8212;'   l.=lへ|~|       そのことを
    |`ー‐/    `ー&#8212;&#8212;  H<,〉|=|       どうか諸君らも
    |  /    、          l|__ノー|       思い出していただきたい
.   | /`ー ~ ′   \   .|ヾ.ニ|ヽ
    |l 下王l王l王l王lヲ|   | ヾ_,| \     つまり・・・・
.     |    ≡         |   `l   \__   我々がその気になれば
    !、           _,,..-'′ /l     | ~'''  finalize は
‐''" ̄| `iー-..,,,_,,,,,....-‐'''"    /  |      |    10年後 20年後ということも
 -&#8212;|  |\          /    |      |   可能だろう・・・・・・・・・・ということ・・・・!
    |   |  \      /      |      |

はい、皆さんの甘い考えはぶち殺されました。よかったですね!ちなみにこれ、冗談とかではなくて、Dalvik が何やってるのかは知りませんが、ファイナライザスレッドがいつまでたっても仕事しないことは、実際ありました(ドヒャー)。そうなったら、もう参照は失われていますから、どうしようもありません。おしまいです。そうならないためにも、明示的に recycle メソッドを呼び出してやる必用があるのです。

そもそも finalizable なオブジェクトと SoftReference の相性は、あまりよくないと思います。これはボクがそう思っているだけで、一般的にどうなのかはちょっとよく分からないです。実際そうなんじゃないかと思っているんですが、javaGC 事情に詳しい方教えてください…あと android というか Dalvik のその辺の事情に詳しい方も色々教えてください…

うあー全然簡単になりませんでした…おしまい。

*1:本当かなあ…Dalvik の GC 事情は詳しくないので、そうでもないかも知れません…