Domanda

Sto usando una libreria di ricerca che consiglia di tenere aperto l'oggetto handle di ricerca per questo può giovare alla cache delle query. Nel corso del tempo ho osservato che la cache tende a gonfiarsi (poche centinaia di megabyte e continua a crescere) e le OOM hanno iniziato a entrare. Non c'è modo di imporre limiti di questa cache né pianificare la quantità di memoria che può usare. Quindi ho aumentato il limite Xmx , ma questa è solo una soluzione temporanea al problema.

Alla fine sto pensando di rendere questo oggetto un referente di java.lang.ref.SoftReference . Quindi, se il sistema esaurisce la memoria libera, lascerebbe andare l'oggetto e ne creerebbe uno nuovo su richiesta. Ciò ridurrebbe un po 'di velocità dopo il nuovo avvio, ma è un'alternativa molto migliore rispetto a colpire OOM.

L'unico problema che vedo su SoftReferences è che non esiste un modo pulito per finalizzare i referenti. Nel mio caso, prima di distruggere l'handle di ricerca, devo chiuderlo, altrimenti il ??sistema potrebbe esaurire i descrittori di file. Ovviamente, posso avvolgere questo handle in un altro oggetto, scrivere un finalizzatore su di esso (o agganciarlo a un ReferenceQueue / PhantomReference) e lasciarlo andare. Ma ehi, ogni singolo articolo su questo pianeta sconsiglia di usare i finalizzatori, e soprattutto - contro i finalizzatori per liberare handle di file (ad esempio Java efficace ed. II, pagina 27.).

Quindi sono un po 'perplesso. Devo ignorare attentamente tutti questi consigli e andare avanti. Altrimenti, ci sono altre alternative praticabili? Grazie in anticipo.

EDIT # 1: il testo seguente è stato aggiunto dopo aver testato del codice come suggerito da Tom Hawtin. A me sembra che uno dei suggerimenti non funzioni o mi manchi qualcosa. Ecco il codice:

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++;
   }
}

Se eseguo lo snippet sopra con -Xmx10m e SoftReferences (come nel codice sopra), sto ottenendo tonnellate di è null :( stampato. Ma se io sostituire il codice con MyReference (decommentando due righe con MyReference e commentando quelle con SoftReference) Ottengo sempre OOM.

Come ho capito dai consigli, avere un riferimento rigido all'interno di MyReference non dovrebbe impedire che l'oggetto colpisca ReferenceQueue , giusto?

È stato utile?

Soluzione

La risposta di Toms è quella corretta, tuttavia il codice che è stato aggiunto alla domanda non è lo stesso di quello che è stato proposto da Tom. Quello che Tom stava proponendo è più simile a questo:

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++;
    }
}

Notare che la grande differenza è che il riferimento reale è all'oggetto che deve essere chiuso. L'oggetto circostante può essere e sarà raccolto nell'immondizia, quindi non colpirai l'OOM, ma avrai comunque la possibilità di chiudere il riferimento. Una volta che lasci il ciclo, anche quella verrà raccolta. Ovviamente, nel mondo reale, probabilmente non potresti rendere res un membro di istanza pubblica

Detto questo, se si tengono riferimenti a file aperti, si corre il rischio molto reale di esaurirli prima di esaurire la memoria. Probabilmente vorrai anche avere una cache LRU per assicurarti di non lasciare più di stick finger in the 500 file aperti. Questi possono anche essere di tipo MyReference in modo che possano anche essere spazzati in caso di necessità.

Per chiarire un po 'su come funziona MySoftBloatReference, la classe di base, ovvero SoftReference, conserva ancora il riferimento all'oggetto che registra tutta la memoria. Questo è l'oggetto che devi liberare per impedire che OOM accada. Tuttavia, se l'oggetto viene liberato, è comunque necessario liberare le risorse utilizzate da Bloat, ovvero Bloat utilizza due tipi di risorse, memoria e handle di file, è necessario liberare entrambe queste risorse oppure eseguire dall'una o dall'altra delle risorse. SoftReference gestisce la pressione sulla risorsa di memoria liberando quell'oggetto, tuttavia è anche necessario rilasciare l'altra risorsa, l'handle del file. Poiché Bloat è già stato liberato, non è possibile utilizzarlo per liberare la risorsa correlata, quindi MySoftBloatReference mantiene un rigido riferimento alla risorsa interna che deve essere chiusa. Una volta che è stato informato che il Bloat è stato liberato, ovvero una volta che il riferimento viene visualizzato in ReferenceQueue, MySoftBloatReference può anche chiudere la risorsa correlata, attraverso il riferimento reale che ha.

EDIT: Aggiornato il codice in modo che si compili quando viene lanciato in una classe. Utilizza uno StringReader per illustrare il concetto di come chiudere Reader, che viene utilizzato per rappresentare la risorsa esterna che deve essere liberata. In questo caso particolare, chiudere quel flusso è effettivamente una no-op, e quindi non è necessario, ma mostra come farlo se è necessario.

Altri suggerimenti

Per un numero finito di risorse: sottoclasse SoftReference . Il riferimento software dovrebbe puntare all'oggetto racchiuso. Un riferimento forte nella sottoclasse dovrebbe fare riferimento alla risorsa, quindi è sempre fortemente raggiungibile. Quando viene letto il ReferenceQueue poll la risorsa può essere chiusa e rimossa dalla cache. La cache deve essere rilasciata correttamente (se un SoftReference stesso viene raccolto in modo errato, non può essere accodato in un ReferenceQueue ).

Fai attenzione a disporre solo di un numero finito di risorse non rilasciate nella cache - elimina le voci precedenti (in effetti, puoi scartare i riferimenti software con cache finita, se adatta alla tua situazione). Di solito è più importante la risorsa non di memoria, nel qual caso una cache di sfratto LRU senza oggetti di riferimento esotici dovrebbe essere sufficiente.

(La mia risposta # 1000. Inserito da London DevDay.)

Ahm.
(Per quanto ne so) Non puoi tenere il bastone da entrambe le estremità. O tieni le tue informazioni o le lasci andare.
Tuttavia ... puoi conservare alcune informazioni chiave che ti permetterebbero di finalizzare. Naturalmente, le informazioni chiave devono essere significativamente più piccole delle "informazioni reali" e non deve contenere le informazioni reali nel suo grafico a oggetti raggiungibile (riferimenti deboli potrebbero esservi utili).
Basandosi sull'esempio esistente (prestare attenzione al campo delle informazioni chiave):

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");
            }

Modifica:
Voglio approfondire il tema "conservare le tue informazioni o lasciarle andare". Supponendo che tu avessi un modo per mantenere le tue informazioni. Ciò avrebbe costretto il GC a deselezionare i tuoi dati, causando la pulizia dei dati solo dopo aver terminato, in un secondo ciclo GC. Questo è possibile - ed è esattamente ciò che serve a finalize (). Dato che hai dichiarato che non vuoi che si verifichi il secondo ciclo, non puoi conservare le tue informazioni (se a - > b allora! B - >! A). il che significa che devi lasciarlo andare.

Edit2:
In realtà, si verificherebbe un secondo ciclo, ma per i tuoi "dati chiave", non i tuoi "dati di gonfiaggio principali". I dati effettivi verrebbero cancellati al primo ciclo.

Edit3:
Ovviamente, la vera soluzione userebbe un thread separato per rimuovere dalla coda di riferimento (non poll (), remove (), blocco sul thread dedicato).

@Paul - grazie mille per la risposta e il chiarimento.

@Ran - Penso che nel tuo codice attuale manchi i ++ alla fine del ciclo. Inoltre, non è necessario eseguire rq.remove () nel ciclo poiché rq.poll () rimuove già il riferimento superiore, non è vero?

Pochi punti:

1) Ho dovuto aggiungere l'istruzione Thread.sleep (1) dopo i ++ nel loop (per entrambe le soluzioni di Paul e Ran) per evitare OOM ma questo è irrilevante per il quadro generale e dipende anche dalla piattaforma. La mia macchina ha una CPU quad-core ed esegue Sun Linux 1.6.0_16 JDK.

2) Dopo aver esaminato queste soluzioni, penso che continuerò a utilizzare i finalizzatori. Il libro di Bloch fornisce i seguenti motivi:

  • non esiste alcuna garanzia che i finalizzatori vengano eseguiti prontamente, quindi non fare mai nulla di critico in un finalizzatore - né esistono garanzie per SoftRererences!
  • Non dipendere mai da un finalizzatore per aggiornare lo stato critico persistente - non lo sono
  • vi è una severa penalità prestazionale per l'uso dei finalizzatori. Nel mio caso peggiore, dovrei finalizzare circa un singolo oggetto al minuto o giù di lì. Penso di poter convivere con questo.
  • usa try / finalmente - oh sì, lo farò sicuramente!

Avere la necessità di creare un'enorme quantità di impalcature solo per quello che sembra un compito semplice non mi sembra ragionevole. Voglio dire, letteralmente, la percentuale di WTF al minuto sarebbe piuttosto alta per chiunque guardasse un simile codice.

3) Purtroppo, non c'è modo di dividere i punti tra Paul, Tom e Ran :( Spero che a Tom non dispiacerà perché ne ha già molti :) :) Giudicare tra Paul e Ran è stato molto più difficile - penso che entrambe le risposte funzionino e siano corrette. Sto solo accettando flag per la risposta di Paul perché è stato valutato più in alto (e ha una spiegazione più dettagliata), ma la soluzione di Ran non è affatto male e probabilmente sarebbe la mia scelta se avessi scelto di implementarlo usando SoftReferences. Grazie ragazzi!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top