Weak Referencesから学ぶJavaにおけるオブジェクトの4種類の参照
各種ドキュメント等を見ていたらWeakReferenceクラスなるものを見つけた。
http://developer.android.com/reference/java/lang/ref/WeakReference.html
これを読んでも英語力のせいか何を言っているのかよくわからなかったけど、メモリ消費の話に繋がることはわかったので、Androidアプリには重要なはずや!と思ってもっと調べて見ることにした。
ちなみに現時点での総Java歴1年そこそこの僕のJavaの「参照」の認識としては"GC絡みで出てくる話で、そのオブジェクトが別のどのオブジェクトからも参照されなくなったらGCの対象になる" といった程度。
信頼と実績のSOを経てたどり着いたのはこちら。
Understanding Weak References
するとJavaの「参照」は4種類もあるらしい。。。知らんかった。こういうのって会社によっては先輩が教えてくれたりするの?
Strong references
これが、上述の僕がこれまで認識してた「参照」
Javaがメモリ管理の必要のない言語っていったって、不用意にオブジェクトを扱うと、参照が残ってしまってGCの対象とならず、メモリを圧迫する原因となる。
例えば画像のキャッシュがよく問題に上がる。負荷が高いディスクからの画像読み込みをなるべく避けるためや、重複した画像を読み込まないようにキャシュ機構を組み込んだ場合、キャッシュはその画像への参照を持ち続けるため、その画像は常にメモリ上に存在することになる。
この状態によるメモリの圧迫を避けるためには、どの画像キャッシュが不要かを判断し、キャッシュを破棄する必要がある。これは結局GCの挙動を再実装しているようなものだ。
Weak references
weak referenceはGCの対象になることを避けられるほど強くない「参照」だ。具体的にはこんな感じで定義する。
WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);
こうすると、weakWidget.get()とすることで、元のオブジェクトのWidgetを取り出すことができる。が、どのオブジェクトからも参照されなくなると(GCの対象になると)weakWidget.get()がnullを返すようになる。
具体的にStrongとどんだけ違うのか、についてはGCの賢さに依存するので一口では言えないらしい。。
Reference queue
weakReferenceがnullを返すようになると、元のオブジェクトは次のGCのタイミングでけされちゃうので、weakReferenceオブジェクト自体は無用になる。この無用なweakReferenceオブジェクトはどこかで処理しないといけない。
これを行うのに便利なReferenceQueueクラスというのがある。weakReferenceのコンストラクタに一緒に渡してやると、そのweakReferenceの実オブジェクトがGC対象になったタイミングでweakReferenceオブジェクトをキューしてくれる。あとは適当なタイミングでキューに溜まったオブジェクトを破棄すればいい。
参照の「弱さ」の違い
これまで"weak references"というざっくりとした言い方をしてきたけど、実は「参照」には以下4種類の強さがある。
- strong
- soft
- weak
- phantom
・・・phantomってかっこいい!(中二病的な意味で)。ちなみに並べた順に強い参照を持つ。・・・は?どう考えてもphantomが最強だろ(中二病的な意味で)。
strongとweakについては触れたので残り2つについて以下で説明する。
Soft references
soft referenceはweakほどGCされないという点を除いてweak referenceと同じ。意味わかんねー。えーと、weak referenceなオブジェクトは次のGCのタイミングでやられちゃうんだけど、softはもうちょっと粘るんだそうだ。
もっと具体的にいうと、メモリに余裕があるうちは粘るらしい。おー!すげー!便利!ということは、キャッシュなんかに最適なわけだね。
それ以外の挙動はsoftと同じ。
大事なことかどうかはわからないけど2回言いました。
Phantom references
さて、みなさんお待ちかねのphantomです。
phantomはザコいweakやsoftと全然違います。さすがphantom!なんと、phantomは元のオブジェクトが取得できないくらいの参照度合いです!get()は常にnullを返します!!
意味わかんねーwwww
唯一の使い道は、ReferenceQueueと組み合わせて使うことにより、元のオブジェクトがいつ死んだかがわかることです。
weakの場合は、どのオブジェクトからも参照されなくなった時点、つまりGCの前にキューされる。いっぽう、phantomは元のオブジェクトがメモリ上から削除されたとき(GCされた後)にキューされる。
そのため、weakの場合はfinalize()によって"蘇生"させることができるため、weakReferenceは死んでるのに元オブジェクトがが生きてるという状態ができてしまう。
※finalizeがなんとなくわかるリンク→http://www.atmarkit.co.jp/fjava/rensai4/troublehacks09/troublehacks09_3.html
が、phantomはそういうことにはならない。get()がいつもnullを返すのも"蘇生"を防ぐため。
で、違いはわかったけど、phantomって誰得?ってところについては、元記事を読んでください。要は、すごく細かいメモリ管理をしたいときにオブジェクトのライフサイクル監視をするとかで使えるんじゃね?的なことが書いてある。でかい画像を順繰りに処理するときとか、finalize()が絡んだ問題対策のためとかじゃね?って書いてある。
でもphantomさんはこんなブログを読んでいる人には扱えるわけがないし、僕もファイナライズとかカタカナで書くとロマサガっぽいなー、くらいのレベルで正直よくわからないので、とりあえずこれでよしとする。なんとなくメモリで困ったときのデバッグとかで使えそうな気がするけど。