あけましておめでとうございます。
とうとう今年の5月で平成が終わってしまいます。
新しい元号がまだ発表されてないので一部の人は困っているようですがどうにかならないものなんでしょうか?
さて、どうにもならないことをどうかしようとすると頭が痛くなるから現実逃避のために Java で遊ぶとしよう。
SoftReference って聞いたことはあるけど実際にプログラムを組む上で使った記憶がないのでこれで遊んでみよう。
java.base モジュールの java.lang.ref パッケージにある public class SoftReference<T> extends Reference<T> クラスです。
API ドキュメントには下記のように記されてます。
メモリー要求に応じてガベージ・コレクタの判断でクリアされるソフト参照オブジェクトです。
ソフト参照は通常、メモリー・センシティブなキャッシュを実装するために使用されます。
ある時点で、オブジェクトがソフト到達可能であると、ガベージ・コレクタが判断したとします。
その時点で、ガベージ・コレクタは、そのオブジェクトへのソフト参照すべてと、強参照のチェーンを経由してそのオブジェクトに到達できるような、ソフト到達可能なほかのオブジェクトへのソフト参照すべてを原子的にクリアすることを選択できます。
同時にまたはあとで、ガベージ・コレクタは、参照キューに登録されているそれらの新しくクリアされたソフト参照をキューに入れます。
ソフト到達可能なオブジェクトへのすべてのソフト参照は、仮想マシンがOutOfMemoryErrorをスローする前にクリアされていることが保証されています。
そうでない場合、ソフト参照がクリアされる時点、またはさまざまなオブジェクトへの一連のソフト参照がクリアされる順序に制約はありません。
ただし、仮想マシンの実装は、最近作成されたソフト参照または最近使用されたソフト参照をクリアしないことが奨励されます。
このクラスの直接のインスタンスは、単純なキャッシュを実装するために使用できます。
このクラスまたは派生したサブクラスは、より洗練されたキャッシュを実装するために、もっと大きなデータ構造でも使用できます。
ソフト参照のリファレントが強到達可能であるかぎり、つまり実際に使用されているかぎり、ソフト参照はクリアされません。
このため、たとえば洗練されたキャッシュは、エントリへの強いリファレントを維持することで、もっとも新しく使用されたエントリが破棄されることを防ぎ、ほかのエントリはガベージ・コレクタの判断で破棄されるようにできます。
つまり、ガベージ・コレクタが参照の使用頻度とヒープの状態をみて参照の解放を勝手にやってくれるので OutOfMemoryError でプログラムが止まってしまうことがない。
ガベージ・コレクタが必死でがんばってくれる!私は感謝するぞ!
で、この SoftReference<T> クラスにはコンストラクタが2種類あります。
SoftReference(T referent)
指定されたオブジェクトを参照する新しいソフト参照を作成します。新しい参照は、どのキューにも登録されません。
パラメータ:referent – 新しいソフト参照が参照するオブジェクト
SoftReference(T referent, ReferenceQueue<? super T> q)
指定されたオブジェクトを参照し、指定されたキューに登録されている新しいソフト参照を作成します。
パラメータ:referent – 新しいソフト参照が参照するオブジェクトq – 参照が登録されるキュー。登録が必要ない場合はnull
メソッドは一つだけあります。
public T get()
参照オブジェクトのリファレントを返します。 プログラムまたはガベージ・コレクタによって、この参照オブジェクトがすでにクリアされている場合、このメソッドはnullを返します。
戻り値:この参照が表すオブジェクト。この参照オブジェクトがクリアされている場合はnull
もの凄く質素なクラスですね。
そう言うことでさっそくお試しプログラムを組んでみました。
package jp.yucchi.softreference_g1gc;
import java.lang.management.ManagementFactory;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Yucchi
*/
public class SoftReference_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<SoftReference<MemoryConsumer>> list = new ArrayList<>();
for (int i = 0; i < 1_000; i++) {
var memoryConsumer = new MemoryConsumer();
var softReference = new SoftReference<>(memoryConsumer, REFEREN_CEQUEU);
list.add(softReference);
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];
}
}
}
無駄にメモリーを消費するインスタンスを1000個作っているだけです。
普通なら OutOfMemoryError でプログラムが止まってしまうはずですが SoftReference を使って賢いガベージ・コレクタの力で完走させようというものです。
プログラムの実行結果は次のようになりました。
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
UsedMemory: 546493104 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
略
UsedMemory: 110148312 G1 Young Generation GC Count: 57 GCTime = 109 G1 Old Generation GC Count: 2 GCTime = 1808
— POLL:java.lang.ref.SoftReference@59fa1d9b —
— POLL:java.lang.ref.SoftReference@28d25987 —
略
— POLL:java.lang.ref.SoftReference@527740a2 —
— POLL:java.lang.ref.SoftReference@13a5fe33 —
UsedMemory: 219243560 G1 Young Generation GC Count: 58 GCTime = 110 G1 Old Generation GC Count: 2 GCTime = 1808
UsedMemory: 328295464 G1 Young Generation GC Count: 58 GCTime = 110 G1 Old Generation GC Count: 2 GCTime = 1808
略
UsedMemory: 6762364184 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
UsedMemory: 6871416088 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
プログラムが終了するまで1分8秒もかかった。
私の7年前のパソコンが壊れたのかと思ってしまった。(^_^;)
NetBeans のプロファイラでどんなかんじなのか確認してみました。
確かに OutOfMemoryError でプログラムが止まらないようにガベージ・コレクタが頑張っているようだけどGC時間長くない?
プログラムの実行結果からも気のせいか GC の回数、累積時間が長いような気がする。
SoftReference ってこんなものなのかなぁ・・・
あっ、なにかプログラムに問題があるのかもしれない。
これは WeakReference で試して比較してみるしかない。
ということで次回に続く
ピンバック: ゆっちのBlog » WeakReference
ピンバック: ゆっちのBlog » PhantomReference