本日のカクテルは「Javaのメモリ管理」です。
ネタ元はかつのりさんのBlogです。
GCについて勘違いしている人が結構多い
JavaというとGC(ガーベッジコレクション)によって不要となったオブジェクトが回収される仕組みになっていますので、一般のプログラマはメモリの確保、解放について考える必要がありません。
ただし、プロフェッショナルなプログラマの方は一般のプログラマの分まで背負い込んで考えてもらう必要があります。
C言語のようにメモリの割り当てと解放を直接管理することはありませんが、まったく別種のメモリ管理が必要となってきます。
参照の種類
「要するに、参照がなくなればGCに回収される、参照があれば回収されない、それだけのことだろう?」
こうおっしゃるお客さんは沢山いらっしゃいます。では、参照にも種類があることをご存じですか?
- 強い参照 (strong reference)
- ソフト参照 (soft reference)
- 弱い参照 (weak reference)
- ファントム参照 (phantom reference)
このあたりについてもかつのりさんが過去に紹介済みなのですが(
java.lang.refパッケージの活用)
こちらでは視点を変えてお話ししてみたいと思います。
- 強い参照で到達できるオブジェクトはGCに回収されない
- 強い参照がなく、ソフト参照到達できるオブジェクトはメモリが一杯であるなどの条件により回収されることがある
- 強い参照やソフト参照がなく、弱い参照到達できるオブジェクトはGCに回収される
- ファントム参照はもはやなくなったオブジェクトへの参照
このように、参照には種類があります。Javaによるメモリ管理とは、これらの参照の特徴を利用してオブジェクトの廃棄をうまく管理してやることになります。
これが、C言語とはまったく別種のメモリ管理というわけです。
参照の作り方
通常、オブジェクトをnewして変数に入れますが、これらはすべて強い参照となります。
そのほかの参照はどうやって作るのでしょうか?
java.lang.refパッケージにこれらの参照を使うためのクラスが用意されています。
- java.lang.ref.SoftReference<T>
- java.lang.ref.WeakReference<T>
- java.lang.ref.PhantomReference<T>
の3つのクラスを利用します。
たとえばソフトな参照を作りたければ、
Object o = new Object();
Reference<Object> ref = new SoftReference<Object>(o);
というようにコンストラクタの引数に対象オブジェクトを渡します。
ソフト参照からオブジェクトを取得する場合は
Object o = ref.get();
if (o == null) {
// GCで回収されている
}
とget()するだけなのですが、GCで回収されている可能性がありますので、常にnullチェックを必要とします。
また、強い参照がなくなってソフトな参照だけになった、などといった参照到達性の変更は
java.lang.ref.ReferenceQueue<T>
を各種Referenceのコンストラクタ引数に渡すことで検出できます。
Object o = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
Reference<Object> ref = new SoftReference<Object>(o, queue);
このようにしておくと、queue.poll()もしくは、queue.remove()とすることで、SoftReferenceのコンストラクタ引数で渡したObjectがソフトな参照だけになった場合にSoftReferenceオブジェクトを取得することができます。
つまり、参照到達性の変更にフックすることができるわけです。
参照の使い方
では、これらの参照はどうやって使うのでしょう?
ソフト参照は、キャッシュを実装する際によく用いられます。
DBを参照して取得してくるような、生成コストの高いオブジェクトはキャッシュしたいところです。
しかし、むやみにキャッシュするとOutOfMemoryエラーとなってしまう…。
そこで、ソフト参照の性質を利用します。
OutOfMemoryとなる前にソフト参照は回収されることが保障されていますから、メモリの確保できる限りはキャッシュしておく、ということができるのですね。
弱参照はjavadocでは正規化マッピングの実装に使われると書いてあるのですが、つまるところ草葉の陰から見守るような使い方をするときに利用します。
Factoryパターンなどで発給したオブジェクトを管理するためにFactory内に参照を持っておきたいというケースを想定しましょう。
しかし、もう使わなくなったよ!ということを検出することは通常ではできません。
I/O関係のclose()メソッドのように使い終わったら必ずこのメソッドを呼び出してね、というルールをjavadocで明示することはできても、コンパイルエラーになるわけではないですから使い手がうっかりすればリークしてしまいますね。
そういうときに弱参照を利用します。Factory内では発給したオブジェクトへの弱参照を保持しておきます。
強い参照がなくなると弱参照だけになり、GCに回収されますから、リークすることはありません。
強い参照が残っている間だけ、該当のオブジェクトにアクセスすることができるのです。
ファントム参照はファイナライザの代わりに利用することができます。
java.lang.Object#finalize()
を利用するよりも柔軟にオブジェクト回収時の処理を行うことができます。
ファントム参照はGC対象だがまだ完全に削除されていないオブジェクトへの参照です。
ここで強い参照に戻せてしまうと困るのでget()メソッドは常にnullを返します。
ですから、ReferenceQueueを利用した使い方しかできません。
ファントム参照が生きている間はオブジェクトはGCによってクリアされません。
ファントム参照をずっと保持しているとメモリリークを起こすことになりますので注意しましょう。
最後に
JavaはGCがメモリ管理するからメモリを意識することはない、せいぜい、無駄な参照があってGC回収対象にならないことがないように気をつければよいだけさ、なんて考えが吹き飛んでいただけたなら幸いです。
共通ライブラリを作る際にはこれらの参照を駆使して、使い手には意識させないメモリ管理をひっそりと行いましょう。
本日のカクテルは楽しんでいただけたでしょうか?
投稿日時 : 2007年7月29日 10:55