2017-09-12 14 views
0

私はAleksey Shipilevのスライド「The String catechism」(https://shipilev.net/talks/joker-Oct2014-string-catechism.pdf、49ffのスライド)で「重複排除剤」の概念を見つけました。多くのJavaプログラマーは、String.intern()のインターンと同様の概念を知っています。
ただし、弱参照が使用されていないと、重複排除器がメモリリークの可能性があります。 弱いハッシュマップを持つ重複排除器の正しい実装がどのように見えるのだろうか。私はオプションBになりがちですが、わかりません。弱いハッシュマップを持つ重複排除器の正しい実装は何ですか?

オプションA: WeakHashMapを使用すれば十分です。 「弱いキー」は、オブジェクトがもはや使用されなくなったときに削除されることを保証する。

サンプル実装:

public class SimpleWeakHashMapDeduplicator { 
    private final WeakHashMap<Object, Object> weakHashMap = new WeakHashMap<>(); 

    public Object deduplicate(Object potentialDuplicate) { 
     if(potentialDuplicate == null) { 
      return null; 
     } else { 
      return weakHashMap.computeIfAbsent(potentialDuplicate, (key)->key); 
     } 
    } 
} 

オプションB: のWeakHashMapを使用するには十分ではありません。 ComplicatedWeakHashMapDeduplicatorのインスタンスは、1つのエントリが値を強く参照するエントリを持つ配列を強く参照する弱いハッシュマップを強く参照するため、すべての値はWeakReferenceでなければなりません。キーだけがマップによって弱く参照されます。どこが間違っていますか?

サンプル実装:

public class ComplicatedWeakHashMapDeduplicator { 
    private final WeakHashMap<Object, WeakReference<Object>> weakHashMap = new WeakHashMap<>(); 

    public Object deduplicate(Object potentialDuplicate) { 
     if(potentialDuplicate == null) { 
      return null; 
     } else { 
      return weakHashMap.computeIfAbsent(potentialDuplicate, WeakReference::new).get(); 
     } 
    } 
} 

あなたはどう思いますか?

+0

WeakReferenceベースの文字列キャッシュは実用的ではありません。あまりにも多くのメモリを消費します。 WeakReferenceはGC時間に大きな影響を与える可能性があります。 –

+0

[Guava WeakInterner](https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Interners.html#newWeakInterner())を参照してください。 – maaartinus

+0

このようなマップは、少数の重複オブジェクトよりもはるかに多くのメモリを消費する可能性があります。 – Holger

答えて

1

私は次のWeakHashMapのJavadocのコメント見落とし:

実装上の注意:のWeakHashMap内の値オブジェクトは 通常の強参照によって保持されています。したがって、 値オブジェクトが直接 のいずれかの独自のキーを強く参照していないようにするか、間接的にキーを破棄しないように注意する必要があります。 値オブジェクトは、 WeakHashMap自体を介して間接的にそのキーを参照する可能性があります。つまり、値オブジェクトは、 という別のキーオブジェクトを強く参照することがあります。このオブジェクトの関連値オブジェクトは、順に、最初の値オブジェクトのキーを指します。 マップ の値が強い参照を保持するマップに依存しない場合、 に対処する1つの方法は、挿入前にWeakReferences 内の値をラップすることです。m.put(key、new WeakReference(value ))、そしてそれぞれ を取得します。

2

あなたは「オプションB」と正しい軌道に乗っていますが、あなたはそれほどまだありません。この行には問題があります。

return weakHashMap.computeIfAbsent(potentialDuplicate, WeakReference::new).get(); 

弱いマップに以前にキャッシュされた値が含まれているとしましょう。あなたはcomputeIfAbsentと電話し、弱いの参照を取得します。あなたがget()の前の簡単なウィンドウの間、ガベージコレクタが指示対象を取り戻すのを防ぐものはありません。その場合は、nullが返されます。

ロジックを少し強くする必要があります。このような何かを試してみてください:

public final class WeakCache<T> { 
    private final WeakHashMap<T, WeakReference<T>> _map = new WeakHashMap<>(); 

    public synchronized T cache(final T value) { 
     if (value == null) { 
      return null; 
     } 

     final WeakReference<T> oldReference = _map.get(value); 

     if (oldReference != null) { 
      final T oldValue = oldReference.get(); 

      if (oldValue != null) { 
       return oldValue; 
      } 
     } 

     _map.put(value, new WeakReference<>(value)); 

     return value; 
    } 
} 

これが漏れるあなたのキャッシュされた値を防ぐことができますが、それはあなたが古い値を解放したときになりたいどれだけ熱心に尋ねる価値があります。あなたの価値が短命になりがちですが、何度も何度もポップアップすることが予想される場合は、それらを長く保持したいかもしれません。その場合は、代わりにSoftReferenceを値ラッパーとして使用することを検討してください。ソフトリファレンスも同様に動作しますが、メモリ参照に直面するまでは参照対象を保持する傾向があります。 Oracleの「サーバー」VM(x64のデフォルト)では、ソフト参照を解放するのではなくヒープを拡張する方が優先されるため、アプリケーションのメモリー使用量が早く制限に達する可能性があり、その時点で到達不能値が取り出されます。それはトレードオフであり、どちらも「すべてに適合する」ソリューションではありません。柔軟な実装では、参照作成をプラグイン可能なポリシーに抽象化することができるため、最初にキャッシュを作成するときに弱参照と参照参照のどちらを選択することも簡単です。

+0

あなたのソリューションは、オプションBの私のソリューションのいくつかの欠陥を解決します。 – mmirwaldt

関連する問題