Android のメモリ管理 #4 SoftReference と WeakReference を活用する

154件のシェア(すこし話題の記事)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

参照方法によってメモリの解放タイミングが異なる

Android ではオブジェクトがヒープ領域にたまりメモリ不足が生じたとき、 ガベージコレクション(GC) が起動して不要なオブジェクトのメモリを開放してくれます。 実装コードではすべてのオブジェクトを手動で開放する必要はなく、基本的には GC の挙動に任せることでメモリ圧迫を軽減できます。特に「不要なオブジェクト」という点が重要で、GC はオブジェクトの参照状態を見て不要かどうか判断します。
参照方法にはいくつか種類があります。通常の参照方法は強参照と呼ばれますが、そのほかにソフト参照(SoftReference)や弱参照(WeakReference)と呼ばれる参照方法があります。これらの参照方法によって GC でメモリから開放されるタイミングが異なるというわけです。
ということで今回は SoftReference と WeakReference の性質と、どのようなケースで活用できるか見ていきたいと思います。

SoftReference と WeakReference の性質

SoftReference と WeakReference の特徴をそれぞれ3点にまとめてみました。

SoftReference

  • オブジェクトの利用頻度とヒープ状況を判断して開放される。
  • アクセスの少ないオブジェクトから開放される。
  • OutOfMemoryError が発生する前に開放してくれる。

WeakReference

  • GC が実行されたとき、参照がひとつもない状態であれば開放される。
  • 一時的に参照を保持しておきたい場合に有効。
  • WeakHashMap クラスで自動的に開放される HashMap も作れる。

こんな感じです。名前からして雰囲気は似ていますが、メモリ解放される条件は大きく違います。 一番大きな違いは GC が実行されたときに参照が残っていても開放されない可能性があるのが SoftReference で、 GC が実行されたときに参照が残っていなければ開放されるのが WeakReference です。少し分かりづらいので、実際の挙動を確認してみたいと思います。

メモリ解放のタイミングを確認してみる

ということで、参照方法によるメモリ解放のタイミングの違いを実際に動作させてみて確認しましょう。 GC (またはメモリリーク) のあとにオブジェクトが存在するかどうか、参照方法別に見ていきたいと思います。

SoftReference はメモリ不足で優先的に開放される

まず SoftReference の挙動を見てみたいと思います。以下は SoftReference を使ってオブジェクトをソフト参照しているだけのとてもシンプルな処理です。

// Integer オブジェクトの弱参照を保持する
Integer integer = new Integer(123456);
SoftReference<Integer> ref = new SoftReference<Integer>(integer);

// SoftReference の中身を取得・表示
Integer i = ref.get();
Log.d(TAG, "i=" + i);

// 強参照を全て無くす
integer = null;
i = null;
System.gc();
Log.d(TAG, "GC");

// GC 実行後に中身を取得・表示
i = ref.get();
Log.d(TAG, "i=" + i);

実行結果

特にメモリ不足が起きているわけではないので GC は「このオブジェクトはまだ開放しなくて大丈夫だ」と判断するため、メモリ解放は行われません。
このコードにメモリ不足が起きるコードを追加します。

// Integer オブジェクトの弱参照を保持する
Integer integer = new Integer(123456);
SoftReference<Integer> ref = new SoftReference<Integer>(integer);

// SoftReference の中身を取得・表示
Integer i = ref.get();
Log.d(TAG, "i=" + i);

// 強参照を全て無くす
integer = null;
i = null;

// メモリ負荷をかける
try {
	HashMap<String, Byte[]> map = new HashMap<String, Byte[]>();
	for (int j = 0; j < 100000; j++) {
		Byte[] v = new Byte[10000];
		String k = String.valueOf(System.currentTimeMillis());
		map.put(k, v);
	}
} catch (OutOfMemoryError oome) {
	Log.d(TAG, "OutOfMemoryError!!");
}

// OutOfMemoryError 発生後に中身を取得・表示
i = ref.get();
Log.d(TAG, "i=" + i);
[/java]
</p>

<p>
<strong>実行結果</strong><br />
<a href="https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2012/10/ref02.png"><img src="https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2012/10/ref02-640x356.png" alt="" title="ref02" width="640" height="356" class="alignnone size-medium wp-image-32794" /></a>
</p>

<p>
メモリ不足が起きているので GC は「<strong>やばい!</strong>」と判断し、SoftReference で保持しているオブジェクトを開放します。
以上のように、 SoftReference は<strong>ヒープ状況によってメモリ解放されるタイミングが異なる参照</strong>であることがわかります。
</p>

<h3>WeakReference は参照がなくなるとメモリ解放される</h3>
<p>
次に WeakReference の動作を見てみます。SoftReference と同じ処理を WeakReference に変えて実行してみます。
</p>

<p>

// Integer オブジェクトの弱参照を保持する
Integer integer = new Integer(123456);
WeakReference<Integer> ref = new WeakReference<Integer>(integer);

// WeakReference の中身を取得・表示
Integer i = ref.get();
Log.d(TAG, "i=" + i);

// 強参照を全て無くす
integer = null;
i = null;
System.gc();
Log.d(TAG, "GC");

// GC 実行後に中身を取得・表示
i = ref.get();
Log.d(TAG, "i=" + i);

実行結果

GC を実行すると、ref が保持していたオブジェクトが破棄されます。これは WeakReference.get() を強参照していた integer , i の参照を消したためです。それにより ref がどこからも参照されなくなり、メモリ解放の対象になりました。 このように WeakReference はどこからも参照されていない状態だとメモリ解放される参照であることがわかりました。

Tips

使いどころによってはとても便利そうな SoftReference と WeakReference ですが、 オブジェクトが開放される基準がすこし複雑なので、誤った使いかたをしてしまう可能性も無きにしろあらずです。 ということで注意点などを Tips としてまとめました。少しだけですけど参考にしていただければと思います。

SoftReference を Bitmap キャッシュに使用してはいけない

使えそうなケースとして一番はじめに思いつきそうなところですが、 SoftReference は Bitmap オブジェクトのキャッシュには使用してはいけません。公式リファレンスにも書いてあります。

http://developer.android.com/reference/java/lang/ref/SoftReference.html

理由は、ソフト参照は状況によってオブジェクトの破棄のタイミングが変わってくるためです。言い換えれば何をするかわからない、といったところがキャッシュ機構で使うには非効率的であるということのようです。キャッシュにはLruCacheクラスを使うことが推奨されているので、そちらを使うようにしましょう。LruCache についてはまた別の記事で紹介したいと思います。

AsyncTask に View の参照を渡したいときには WeakReference を使う

非同期処理は、処理完了後に必ずしもそのタスクを実行した Activity が存在しているとは限りません。 View が GC で破棄されるタイミングは Activity が破棄されたときと同じであるほうが好ましいので、 AsyncTask が View の参照を保持しないよう、 WeakReference を使って参照するようにしましょう。

まとめ

参照方法を理解することはメモリリークを事前に防ぐ上でとても重要です。 SoftReference と WeakReference の特性を正しく理解して、実装時に「どの参照方法が適切か」考えて実装することができればワンランク上の開発者としてステップアップできると思います。 しかしながら正しく理解していない状態で実装してしまうと「消えてはいけないところでオブジェクトが消えちゃってる!」というバグに遭遇しかねないので、ご注意ください。
以下のリンクも大変参考になる記事です。ぜひ理解を深めていきましょう!

参考