質問

検索ライブラリを使用していますが、これは検索ハンドルオブジェクトを開いたままにしておくことをお勧めします。時間がたつにつれて、キャッシュが肥大化する傾向があり(数百メガで成長し続けます)、OOMが作動し始めました。このキャッシュの制限を強制したり、使用できるメモリ量を計画したりする方法はありません。そこで、 Xmx の制限を増やしましたが、それは問題に対する一時的な解決策に過ぎません。

最終的には、このオブジェクトを java.lang.ref.SoftReference referent にすることを考えています。したがって、システムの空きメモリが少なくなると、オブジェクトは解放され、必要に応じて新しいオブジェクトが作成されます。これは、新規起動後に速度をいくらか低下させますが、これはOOMを押すよりもはるかに優れた代替手段です。

SoftReferencesに関して私が目にする唯一の問題は、参照先を確定するための明確な方法がないことです。私の場合、検索ハンドルを破棄する前に閉じる必要があります。そうしないと、システムがファイル記述子を使い果たす可能性があります。明らかに、このハンドルを別のオブジェクトにラップし、ファイナライザを記述して(またはReferenceQueue / PhantomReferenceにフックして)放すことができます。しかし、この惑星のすべての記事は、ファイナライザーを使用すること、特にファイルハンドルを解放するファイナライザーを使用しないことを推奨しています(たとえば、 Effective Java ed。II、page 27)。

だから私はやや困惑しています。これらのアドバイスをすべて無視して続行する必要があります。そうでなければ、他の実行可能な代替手段はありますか?事前に感謝します。

編集#1:以下のテキストは、Tom Hawtinが提案したいくつかのコードをテストした後に追加されました。私には、提案が機能していないか、何かが欠けているようです。コードは次のとおりです。

class Bloat {  // just a heap filler really
   private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

   private final int ii;

   public Bloat(final int ii) {
      this.ii = ii;
   }
}

// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
   private final T hardRef;

   MyReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      this.hardRef = referent;
   }
}

//...meanwhile, somewhere in the neighbouring galaxy...
{
   ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
   Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
   int i=0;

   while(i<50000) {
//      set.add(new MyReference<Bloat>(new Bloat(i), rq));
      set.add(new SoftReference<Bloat>(new Bloat(i), rq));

//      MyReference<Bloat> polled = (MyReference<Bloat>) rq.poll();
      SoftReference<Bloat> polled = (SoftReference<Bloat>) rq.poll();

      if (polled != null) {
         Bloat polledBloat = polled.get();
         if (polledBloat == null) {
           System.out.println("is null :(");
         } else {
           System.out.println("is not null!");
         }
      }
      i++;
   }
}

上記のコードのように -Xmx10m とSoftReferencesを使用して上記のスニペットを実行すると、大量の is null:(が出力されます。しかし、コードを MyReference に置き換えます(MyReferenceで2行のコメントを外し、SoftReferenceで2行をコメントアウトします)常にOOMを取得します。

アドバイスから理解したように、 MyReference 内にハード参照を設定しても、オブジェクトが ReferenceQueue にヒットすることを防ぐべきではありませんか?

役に立ちましたか?

解決

Tomsの回答は正しいものですが、質問に追加されたコードはTomが提案したものと同じではありません。トムが提案していたものは、次のようになります。

class Bloat {  // just a heap filler really
    public Reader res;
    private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

    private final int ii;

    public Bloat(final int ii, Reader res) {
       this.ii = ii;
       this.res = res;
    }
 }

 // as recommended by Tom Hawtin
 class MySoftBloatReference extends SoftReference<Bloat> {
    public final Reader hardRef;

    MySoftBloatReference(Bloat referent, ReferenceQueue<Bloat> q) {
       super(referent, q);
       this.hardRef = referent.res;
    }
 }

 //...meanwhile, somewhere in the neighbouring galaxy...
 {
    ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
    Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
    int i=0;

    while(i<50000) {
        set.add(new MySoftBloatReference(new Bloat(i, new StringReader("test")), rq));

        MySoftBloatReference polled = (MySoftBloatReference) rq.poll();

        if (polled != null) {
            // close the reference that we are holding on to
            try {
                polled.hardRef.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        i++;
    }
}

大きな違いは、ハード参照が閉じる必要のあるオブジェクトに対するものであることに注意してください。周囲のオブジェクトはガベージコレクトされる可能性があるため、OOMにヒットすることはありませんが、参照を閉じる可能性はあります。ループを抜けると、ガベージコレクションも行われます。もちろん、現実の世界では、おそらく res をパブリックインスタンスメンバーにしないでしょう。

とはいえ、開いているファイル参照を保持している場合、メモリを使い果たす前にそれらを使い果たすという非常に現実的なリスクがあります。また、LRUキャッシュを使用して、 500本のオープンファイルを指でつまらないようにしてください。これらはMyReference型にすることもできるため、必要に応じてガベージコレクションを行うこともできます。

MySoftBloatReferenceの動作を少し明確にするために、基本クラス、つまりSoftReferenceは、すべてのメモリを占有しているオブジェクトへの参照を保持しています。これは、OOMの発生を防ぐために解放する必要があるオブジェクトです。ただし、オブジェクトが解放された場合でも、Bloatが使用しているリソース、つまりBloatがメモリとファイルハンドルの2種類のリソースを使用しているリソースを解放する必要があります。これらのリソースは両方とも解放する必要があります。いずれかのリソースが不足しています。 SoftReferenceは、そのオブジェクトを解放することでメモリリソースへの圧力を処理しますが、他のリソースであるファイルハンドルも解放する必要があります。 Bloatは既に解放されているため、関連するリソースを解放するために使用することはできません。そのため、MySoftBloatReferenceは、閉じる必要のある内部リソースへのハード参照を保持します。 Bloatが解放されたことが通知されると、つまりReferenceReferenceで参照が有効になると、MySoftBloatReferenceは、それが持っているハード参照を介して関連リソースを閉じることもできます。

編集:クラスにスローされたときにコンパイルされるようにコードを更新しました。 StringReaderを使用して、リーダーを閉じる方法の概念を示します。リーダーは、解放する必要のある外部リソースを表すために使用されています。この特定のケースでは、そのストリームを閉じることは事実上何もしないので必要ありませんが、必要な場合にそれを行う方法を示します。

他のヒント

有限数のリソースの場合:サブクラス SoftReference 。ソフト参照は、囲んでいるオブジェクトを指す必要があります。サブクラスの強い参照はリソースを参照する必要があるため、常に強く到達可能です。 ReferenceQueue poll を読み取ると、リソースを閉じてキャッシュから削除できます。キャッシュを正しく解放する必要があります( SoftReference 自体がガベージコレクションされる場合、 ReferenceQueue にキューに入れることはできません)。

キャッシュでリリースされていないリソースの数が有限であることに注意してください-古いエントリを削除してください(実際、状況に応じて、有限キャッシュの場合はソフト参照を破棄できます)。通常、それがより重要なのは非メモリリソースである場合です。この場合、エキゾチックな参照オブジェクトのないLRUエビクションキャッシュで十分です。

(私の答え#1000。LondonDevDayから投稿。)

ああ。
(私の知る限り)両端からスティックを保持することはできません。情報を保持するか、手放します。
ただし...ファイナライズを可能にする重要な情報を保持できます。もちろん、重要な情報は「実際の情報」よりもかなり小さくなければなりません。到達可能なオブジェクトグラフに実際の情報を含めることはできません(弱い参照が役立つ場合があります)。
既存の例の上に構築します(重要な情報フィールドに注意してください):

public class Test1 {
    static class Bloat {  // just a heap filler really
        private double a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;

        private final int ii;

        public Bloat(final int ii) {
            this.ii = ii;
        }
    }

    // as recommended by Tom Hawtin
    static class MyReference<T, K> extends SoftReference<T> {
        private final K keyInformation;

        MyReference(T referent, K keyInformation, ReferenceQueue<? super T> q) {
            super(referent, q);
            this.keyInformation = keyInformation;
        }

        public K getKeyInformation() {
            return keyInformation;
        }
    }

    //...meanwhile, somewhere in the neighbouring galaxy...
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
        Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
        int i = 0;

        while (i < 50000) {
            set.add(new MyReference<Bloat, Integer>(new Bloat(i), i, rq));

            final Reference<? extends Bloat> polled = rq.poll();

            if (polled != null) {
                if (polled instanceof MyReference) {
                    final Object keyInfo = ((MyReference) polled).getKeyInformation();
                    System.out.println("not null, got key info: " + keyInfo + ", finalizing...");
                } else {
                    System.out.println("null, can't finalize.");
                }
                rq.remove();
                System.out.println("removed reference");
            }

編集:
「あなたの情報を保持するか、手放すか」について詳しく説明したいと思います。情報を保持する何らかの方法があると仮定します。これにより、GCは強制的にデータのマークを解除し、2回目のGCサイクルで、データの処理が完了して初めて実際にデータが消去されます。これは可能です-そして、まさにそのfinalize()の目的です。 2番目のサイクルを発生させたくないと述べたため、情報を保持することはできません(a-&gt; bの場合、!b-&gt;!aの場合)。つまり、手放す必要があります。

Edit2:
実際には、2番目のサイクルが発生しますが、「主要な膨張データ」ではなく「鍵データ」についてです。実際のデータは最初のサイクルでクリアされます。

Edit3:
明らかに、実際のソリューションでは、参照キューから削除するために別のスレッドを使用します(poll()、remove()、専用スレッドでブロックしないでください)。

@Paul-回答と説明をありがとう。

@Ran-現在のコードでは、ループの最後にi ++が欠落していると思います。また、rq.poll()はすでにトップリファレンスを削除しているため、ループ内でrq.remove()を実行する必要はありませんか?

いくつかのポイント:

1)OOMを回避するために、ループ内のi ++の後にThread.sleep(1)ステートメントを追加する必要がありました(PaulとRanの両方のソリューションの場合)。私のマシンにはクアッドコアCPUがあり、Sun Linux 1.6.0_16 JDKを実行しています。

2)これらの解決策を見た後、ファイナライザーを使い続けると思います。 Blochの本には次の理由があります。

  • ファイナライザが即座に実行されるという保証はないため、ファイナライザでタイムクリティカルなことは絶対に行わないでください。また、SoftRererencesの保証もありません!
  • 重要な永続状態を更新するためにファイナライザーに依存することはありません-私はそうではありません
  • ファイナライザを使用するとパフォーマンスが大幅に低下します-最悪の場合、1分あたり約1つのオブジェクトについてファイナライズすることになります。私はそれで生きることができると思います。
  • try / finallyを使用します-ああ、そうです、間違いなくそうします!

単純なタスクと思われるもののためだけに膨大な量の足場を作成する必要があることは、私には合理的ではないようです。 文字通り、1分あたりのWTFレートは、そのようなコードを見る他の誰にとっても非常に高いでしょう。

3)さらに、ポール、トム、ランの間でポイントを分割する方法はありません:( トムがすでにたくさん持っているので気にしないことを望みます:)ポールとランの間の判断ははるかに困難でした-私は両方の答えが機能し、正しいと思います。 Paulの回答に高い評価が付けられた(そしてより詳細な説明がある)ため、acceptフラグを設定するだけですが、Ranのソリューションはまったく悪くなく、SoftReferencesを使用して実装することを選択した場合、おそらく私の選択でしょう。みんなありがとう!

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top