9

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

最終的に、私はこのオブジェクトをの指示対象にすることを考えていますjava.lang.ref.SoftReference。したがって、システムの空きメモリが不足すると、オブジェクトが解放され、オンデマンドで新しいオブジェクトが作成されます。これにより、新たに開始した後の速度がいくらか低下しますが、これはOOMを押すよりもはるかに優れた代替手段です。

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

だから私は少し戸惑っています。これらのアドバイスをすべて慎重に無視して続行する必要があります。そうでなければ、他の実行可能な選択肢はありますか?前もって感謝します。

編集#1:トムホーティンによって提案されたようにいくつかのコードをテストした後、以下のテキストが追加されました。私には、提案が機能していないか、何かが足りないように見えます。コードは次のとおりです。

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でコメントアウトする)に置き換えると、常にOOMになります。

アドバイスから理解したように、内部にハードリファレンスがあるからといって、MyReferenceオブジェクトが当たるのを防ぐことはできませんReferenceQueueよね?

4

4 に答える 4

7

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

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

(私の答え#1000。LondonDevDayから投稿されました。)

于 2009-10-28T17:53:49.243 に答える
5

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

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 は、閉じる必要がある内部リソースへのハード参照を保持します。

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

于 2009-12-03T22:11:26.397 に答える
2

ああ。
(私が知る限り)スティックを両端から持つことはできません。情報を保持するか、手放すかのどちらかです。
ただし...最終決定を可能にするいくつかの重要な情報を保持することができます. もちろん、重要な情報は「実際の情報」よりも大幅に小さくする必要があり、到達可能なオブジェクト グラフに実際の情報が含まれていてはなりません (弱い参照が役立つ場合があります)。
既存の例に基づいて作成します (キー情報フィールドに注意してください):

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-->b の場合 !b-->!a)。つまり、手放さなければなりません。

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

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

于 2009-12-06T15:19:11.760 に答える
0

@Paul - 回答と説明に感謝します。

@Ran - 現在のコードでは、ループの最後に i++ がないと思います。また、rq.poll() はすでにトップ参照を削除しているため、ループ内で rq.remove() を実行する必要はありませんね。

いくつかのポイント:

1) OOM を回避するために (Paul と Ran の両方のソリューションに対して) ループ内の i++ の後に Thread.sleep(1) ステートメントを追加する必要がありましたが、それは全体像とは無関係であり、プラットフォームにも依存します。私のマシンにはクアッドコア CPU があり、Sun Linux 1.6.0_16 JDK を実行しています。

2)これらのソリューションを見た後、私はファイナライザーを使い続けると思います。ブロックの本は、次の理由を提供します。

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

単純なタスクのように見えるもののために膨大な量の足場を作成する必要があることは、私には合理的ではありません。つまり、文字通り、1 分あたりの WTF は、そのようなコードを見ている他の人にとっては非常に高いということです。

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

于 2009-12-07T11:46:50.840 に答える