このエントリーは、SoftReference エントリーの続きです。
SoftReference で組んだプログラムの実行結果が想像していたのと違っていたので今度は WeakReference で遊んでみます。
まず、WeakReference ってどんなものなのか調べてみます。
APIドキュメントには次のように記されてます。
弱参照オブジェクトです。弱参照オブジェクトは、その弱参照オブジェクトのリファレントがファイナライズ可能になり、ファイナライズされ、そして再生されることを阻止することはありません。
弱参照は、ほとんどの場合で正規化マッピングを実装するために使用されます。
ある時点で、オブジェクトが弱到達可能であると、ガベージ・コレクタが判断したとします。
その時点で、ガベージ・コレクタは、そのオブジェクトへの弱参照すべてと、強参照およびソフト参照のチェーンを経由してそのオブジェクトに到達できるような、ほかの弱到達可能なオブジェクトへの弱参照すべてを、原子的にクリアします。
同時に、ガベージ・コレクタは以前に弱到達可能なオブジェクトがすべてファイナライズ可能であることを宣言します。
同時にまたはあとで、ガベージ・コレクタは、参照キューに登録されているそれらの新しくクリアされた弱参照をキューに入れます。
WeakReference では弱到達可能になったら問答無用で何も考えずに GC が働くときに無条件で参照を解放されるってことかな。
SoftReference のように参照の使用頻度やヒープの状況を考慮して参照を解放するのとは大違いですね。
ちなみにこの WeakReference はこの特性をいかして WeakHashMap に使われていますね。
java.util.WeakHashMap: キーが弱可到達になったらエントリごと削除する Map
WeakReference クラスにはコンストラクタが二つあります。
WeakReference(T referent)
指定されたオブジェクトを参照する、新しい弱参照を作成します。新しい参照は、どのキューにも登録されません。
WeakReference(T referent, ReferenceQueue<? super T> q)
指定されたオブジェクトを参照し、指定されたキューに登録されている新しい弱参照を作成します。
パラメータ:referent – 新しい弱参照が参照するオブジェクトq – 参照が登録されるキュー。登録が必要ない場合はnull
メソッドは無いですね。
それではプログラムを組んで試してみます。
前回組んだプログラムと同じように無駄にメモリーを消費するインスタンスを1000個作っているだけです。
今回のプログラムには無駄にメモリーを消費するオブジェクトに finalize() メソッドを実装しました。
特にそれによって何かするわけでなく呼ばれたことを確認するためだけです。
package jp.yucchi.weakreference_g1gc;
import java.lang.management.ManagementFactory;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Yucchi
*/
public class WeakReference_G1GC {
private static final ReferenceQueue<MemoryConsumer> REFEREN_CEQUEU = new ReferenceQueue<>();
public static void main(String[] args) {
System.out.println(System.getProperty("os.name") + " "
+ System.getProperty("os.version") + " " + System.getProperty("os.arch"));
System.out.println("JDK " + System.getProperty("java.version") + System.lineSeparator());
List<WeakReference<MemoryConsumer>> list = new ArrayList<>();
for (int i = 0; i < 1_000; i++) {
var memoryConsumer = new MemoryConsumer();
var weakReference = new WeakReference<>(memoryConsumer, REFEREN_CEQUEU);
list.add(weakReference);
checkStatus();
}
}
private static void checkStatus() {
var memoryBean = ManagementFactory.getMemoryMXBean();
var usage = memoryBean.getHeapMemoryUsage();
var usedMemory = usage.getUsed();
var gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
var gcStatus = new StringBuilder();
gcMXBeans.forEach(e -> {
gcStatus
.append(e.getName())
.append(" ")
.append("GC Count: ")
.append(e.getCollectionCount())
.append(" ")
.append("GCTime = ")
.append(e.getCollectionTime())
.append(" ");
});
System.out.print("UsedMemory: " + usedMemory + " ");
System.out.println(gcStatus);
Reference<?> ref;
while ((ref = REFEREN_CEQUEU.poll()) != null) {
System.out.println(" --- POLL:" + ref + " ---");
}
}
private static class MemoryConsumer {
private final byte[] buff;
MemoryConsumer() {
this.buff = new byte[100 * 1024 * 1024];
}
@Override
protected void finalize() {
System.out.println(" --- finalize() ---");
}
}
}
プログラムの実行結果は次のようになりました。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
— finalize() —
— finalize() —
— finalize() —
— finalize() —
UsedMemory: 546492432 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.WeakReference@d8355a8 —
— POLL:java.lang.ref.WeakReference@59fa1d9b —
— POLL:java.lang.ref.WeakReference@28d25987 —
— POLL:java.lang.ref.WeakReference@4501b7af —
— finalize() —
UsedMemory: 219385672 G1 Young Generation GC Count: 2 GCTime = 6 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.WeakReference@523884b2 —
UsedMemory: 332631880 G1 Young Generation GC Count: 2 GCTime = 6 G1 Old Generation GC Count: 0 GCTime = 0
略
UsedMemory: 1201289840 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1310341744 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1419393648 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
SoftReference とは大違いです。
プログラムの処理時間も15秒と非常に短い時間です。
GC 累積時間もぜんぜん違います。
NetBeans のプロファイラで確認してみます。
SoftReference と比べると使用しているヒープメモリーサイズも全然少ないし、GC タイムも雲泥の差がある。
これを見るとなるだけ SoftReference は使いたくないと思ってしまう。
でも、SoftReference だったらソフト到達可能なオブジェクトへのすべてのソフト参照は、仮想マシンが OutOfMemoryError をスローする前にクリアされていることが保証されている。
SoftReference はメモリの容量に応じてスケールするキャッシュ用って感じかな。
WeakReference は弱参照オブジェクトが弱到達可能になったらガベージ・コレクタでクリアしたい時に使う(キャッシュしない)。
API ドキュメントの文章が難しく書かれているのであまり自信はないけどこのような違いがあるようだ。
プログラムの実行結果からもその様子が少しは解ります。
ちなみに SoftReference と WeakReference の違いはプログラムの出力結果のガベージ・コレクタの動作ではっきりと解ります。
WeakReference の弱到達可能なオブジェクトは Young Generation 領域に対するマイナー GC ですぐ回収されています。
それに対して SoftReference のソフト到達可能オブジェクトは G1 Old Generation 領域に移されヒープが不足して OutOfMemoryError をスローしないところまで回収されないようです。
SoftReference: UsedMemory: 6871416088 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
WeakReference: UsedMemory: 1419393648 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
なかなか面白い仕組みになっていますね。
強参照しか普段使わない私にとって新鮮な気分です。
しかし、これって JDK 1.2 から追加された古い API のようです。(^_^;)
いったいどれだけのプログラマが意識して使っているのか、いや・・・使わざるを得なかったのか・・・
さて、次は PhantomReference を試してみよう。
たぶん、続く。