1概要

この記事では、

java.util

パッケージの


WeakHashMap


を見ていきます。

データ構造を理解するために、ここでそれを使用して単純なキャッシュ実装をロールアウトします。ただし、これはマップの機能を理解するためのものであり、独自のキャッシュ実装を作成することはほとんど常に悪い考えです。

簡単に言うと、

WeakHashMap

はハッシュテーブルベースの

Map

インタフェースの実装であり、そのキーはhttps://docs.oracle.com/javase/8/docs/api/java/lang/ref/WeakReference.htmlです。[弱参照__]タイプ。


WeakHashMap

のエントリは、そのキーが通常使用されなくなったときに自動的に削除されます。つまり、


参照


。ガベージコレクション(GC)プロセスがキーを破棄すると、そのエントリはマップから事実上削除されるため、このクラスの動作は他の

Map

実装とは多少異なります。


2強い、弱い、弱い参照


WeakHashMap

がどのように機能するかを理解するには、


WeakReference

クラス

を見る必要があります。これは、

WeakHashMap

実装のキーの基本的な構成要素です。 Javaには、主に3つのタイプの参照があります。これについては、次のセクションで説明します。


2.1. 強い参照

強い参照は、私たちが日々のプログラミングで使う最も一般的なタイプの

Reference

です:

Integer prime = 1;

変数

prime

には、値1を持つ

Integer

オブジェクトへの

strong reference

があります。それを指す強い参照を持つオブジェクトは、GCには適格ではありません。

** 2.2. ソフトリファレンス

簡単に言うと、それを指し示す


SoftReference


を持つオブジェクトは、削除されるまでガベージコレクションされません。 JVMは絶対にメモリを必要とします。

Javaで

SoftReference

を作成する方法を見てみましょう。

Integer prime = 1;
SoftReference<Integer> soft = new SoftReference<Integer>(prime);
prime = null;


prime

オブジェクトはそれを指し示す強い参照を持っています。

次に、

prime

強い参照を弱い参照にまとめています。

その強い参照を

null

にした後、

prime

オブジェクトはGCに適格ですが、JVMが絶対にメモリーを必要とする場合にのみ収集されます。


2.3. 弱い参照

弱い参照によってのみ参照されるオブジェクトは熱心にガベージコレクションされます。その場合、GCはメモリが必要になるまで待機しません。

次のようにして、Javaで

WeakReference

を作成できます。

Integer prime = 1;
WeakReference<Integer> soft = new WeakReference<Integer>(prime);
prime = null;


prime

reference

null

を作成すると、それを指す他の強力な参照がないため、

prime

オブジェクトは次のGCサイクルでガベージコレクションされます。


WeakReference

型の参照は、

WeakHashMap

のキーとして使用されます。


3効率的なメモリキャッシュとしての

WeakHashMap


大きな画像オブジェクトを値として、画像名をキーとして保持するキャッシュを構築したいとしましょう。その問題を解決するために適切なマップ実装を選択したいと思います。

値オブジェクトが大量のメモリを占有する可能性があるため、単純な

HashMap

を使用することはお勧めできません。さらに、それらがアプリケーションで使用されなくなった場合でも、GCプロセスによってキャッシュから回収されることは決してありません。

理想的には、GCが未使用のオブジェクトを自動的に削除できる

Map

実装が必要です。大きな画像オブジェクトのキーが我々のアプリケーションのどこにも使用されていない場合、そのエントリはメモリから削除されます。

幸い、

WeakHashMap

はまさにこれらの特徴を持っています。

WeakHashMap

をテストし、それがどのように動作するかを見てみましょう。

WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image__id");
UniqueImageName imageName = new UniqueImageName("name__of__big__image");

map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));

imageName = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);


BigImage

オブジェクトを格納する

WeakHashMap

インスタンスを作成します。値として

BigImage

オブジェクトを、キーとして

imageName

オブジェクト参照を入れています。

imageName



WeakReference

タイプとしてマップに格納されます。

次に、

imageName

参照を

null

に設定します。したがって、

bigImage

オブジェクトを指す参照はもうありません。

WeakHashMap

のデフォルトの動作は、次のGCで参照されていないエントリを再利用することです。したがって、このエントリは次のGCプロセスによってメモリから削除されます。

JVMにGCプロセスの起動を強制するために

System.gc()

を呼び出しています。

GCサイクルの後、

WeakHashMap

は空になります。

WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImageFirst = new BigImage("foo");
UniqueImageName imageNameFirst = new UniqueImageName("name__of__big__image");

BigImage bigImageSecond = new BigImage("foo__2");
UniqueImageName imageNameSecond = new UniqueImageName("name__of__big__image__2");

map.put(imageNameFirst, bigImageFirst);
map.put(imageNameSecond, bigImageSecond);

assertTrue(map.containsKey(imageNameFirst));
assertTrue(map.containsKey(imageNameSecond));

imageNameFirst = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.size() == 1);
await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.containsKey(imageNameSecond));


imageNameFirst

参照だけが

null

に設定されていることに注意してください。

imageNameSecond

参照は変更されません。 GCが起動された後、マップには

imageNameSecond

というエントリが1つだけ含まれます。

4.まとめ

この記事では、

java.util.WeakHashMap

がどのように機能するかを完全に理解するために、Javaでの参照のタイプを調べました。

WeakHashMap

の動作を活用した単純なキャッシュを作成し、予想通りに動作するかどうかをテストします。

これらすべての例とコードスニペットの実装は、https://github.com/eugenp/tutorials/tree/master/java-collections-maps[GitHubプロジェクト]にあります。そのままインポートして実行するのは簡単です。