2009-10-28 9 views
7

Ich benutze eine Suchbibliothek, die darauf hinweist, dass das Such-Handle-Objekt geöffnet bleiben muss, damit der Abfrage-Cache genutzt werden kann. Im Laufe der Zeit habe ich beobachtet, dass der Cache aufgebläht wird (einige hundert MBs und wächst weiter) und OOMs begannen sich einzuschalten. Es gibt keine Möglichkeit, Grenzen dieses Cache zu erzwingen oder zu planen, wie viel Speicher er verwenden kann. Also habe ich den Grenzwert XMX erhöht, aber das ist nur eine vorübergehende Lösung für das Problem.Würde den SoftReference-Referenten korrekt abschließen

Schließlich denke ich, dieses Objekt zu einem Referent von java.lang.ref.SoftReference machen. Wenn also der freie Speicher des Systems knapp wird, würde das Objekt losgelassen und bei Bedarf ein neues erstellt. Dies würde die Geschwindigkeit nach dem Neustart verringern, aber dies ist eine viel bessere Alternative als OOM zu treffen.

Das einzige Problem, das ich über SoftReferences sehe, ist, dass es keinen sauberen Weg gibt, ihre Referenten fertigzustellen. In meinem Fall muss ich das Such-Handle löschen, bevor ich es schließe, sonst könnte das System keine Dateideskriptoren mehr haben. Offensichtlich kann ich dieses Handle in ein anderes Objekt einfügen, einen Finalizer darauf schreiben (oder in eine ReferenceQueue/PhantomReference einhaken) und loslassen. Aber hey, jeder einzelne Artikel auf diesem Planeten rät davon ab, Finalizer zu verwenden, und insbesondere - gegen Finalizer zum Freigeben von Datei-Handles (z. B. Effektive Java ed. II, Seite 27.).

So bin ich etwas verwirrt. Sollte ich all diese Ratschläge sorgfältig ignorieren und weitermachen? Gibt es sonst noch Alternativen? Danke im Voraus.

EDIT # 1: Der folgende Text wurde nach dem Testen von Code hinzugefügt, wie von Tom Hawtin vorgeschlagen. Mir scheint, dass entweder der Vorschlag nicht funktioniert oder mir etwas fehlt. Hier ist der Code:

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

Wenn ich die Schnipsel oben mit -Xmx10m und SoftReferences (wie im Code oben) laufen, ich bin immer Tonnen von is null :( gedruckt. Aber wenn ich den Code durch MyReference ersetze (zwei Zeilen mit MyReference auskommentieren und mit SoftReference auskommentieren), bekomme ich immer OOM.

Wie ich aus dem Rat verstanden, sollte harte Referenz innerhalb MyReference nicht verhindern Objekt schlagen ReferenceQueue, richtig?

Antwort

5

Toms Antwort die richtige ist, aber der Code, der auf die Frage hinzugefügt worden ist, ist nicht das gleiche wie das, was von Tom vorgeschlagen wurde.Was Tom sieht eher wie dies vorschlägt:

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

Beachten Sie, dass der große Unterschied ist, dass die harte Referenz auf das Objekt, das geschlossen werden muss. Das umgebende Objekt kann und wird Müll gesammelt werden, so dass Sie nicht die OOM treffen, aber Sie erhalten trotzdem eine Chance, die Referenz zu schließen. Sobald Sie die Schleife verlassen, wird das auch Müll gesammelt. Natürlich würden Sie in der realen Welt wahrscheinlich kein öffentliches Instanzmitglied res machen.

Das heißt, wenn Sie offene Dateireferenzen halten, dann laufen Sie ein sehr reales Risiko von denen aus, bevor Sie nicht genügend Arbeitsspeicher haben. Sie möchten wahrscheinlich auch einen LRU-Cache haben, um sicherzustellen, dass Sie nicht mehr als Finger in der Luft halten 500 offene Dateien. Diese können auch vom Typ MyReference sein, so dass sie bei Bedarf auch Müll gesammelt werden können.

Um ein wenig darüber zu klären, wie MySoftBloatReference funktioniert, enthält die Basisklasse, also SoftReference, immer noch den Verweis auf das Objekt, das den gesamten Speicher belegt. Dies ist das Objekt, das Sie freigeben müssen, um das OOM zu verhindern. Wenn das Objekt jedoch freigegeben wird, müssen Sie weiterhin die Ressourcen freigeben, die der Aufblähvorgang verwendet, das heißt, Bloat verwendet zwei Arten von Ressourcen, Arbeitsspeicher und ein Dateihandle, beide Ressourcen müssen freigegeben werden oder Sie müssen ausgeführt werden aus der einen oder anderen der Ressourcen. Die SoftReference behandelt den Druck auf die Speicherressource, indem sie das Objekt freigibt. Sie müssen jedoch auch die andere Ressource, das Dateihandle, freigeben. Da Bloat bereits freigegeben wurde, können wir es nicht verwenden, um die zugehörige Ressource freizugeben. Daher behält MySoftBloatReference einen harten Verweis auf die interne Ressource, die geschlossen werden muss. Sobald es informiert worden ist, dass die Aufblähung freigegeben wurde, d. H. Sobald die Referenz in der ReferenceQueue auftaucht, kann MySoftBloatReference auch die zugehörige Ressource durch die harte Referenz, die sie hat, schließen.

EDIT: Der Code aktualisiert, so dass es kompiliert, wenn in eine Klasse geworfen wird. Es verwendet einen StringReader, um das Konzept zu veranschaulichen, wie der Reader geschlossen wird, der zur Darstellung der externen Ressource verwendet wird, die freigegeben werden muss. In diesem speziellen Fall ist das Schließen dieses Streams effektiv ein No-Op und wird daher nicht benötigt, aber es zeigt, wie dies zu tun ist, wenn es benötigt wird.

+0

Wäre es möglich, Ihren Code so zu reparieren, dass er kompiliert? ZB MyReference Konstruktor nimmt Bloat Referent Argument und soll es HardRef zuweisen, aber HardRef ist von einem völlig anderen Typ (ResourceThatMustBeClosed). Kannst du auch erklären, warum Bloat immer noch notwendig ist, wenn wir ResourceThatMustBeClosed haben? PS Ich wäre nicht so bedürftig, wenn diese Frage keine Bonuspunkte hätte: P – mindas

+0

Ich habe den Code aktualisiert und (hoffentlich) ein klares hinzugefügt Erklärung, wie es funktioniert? Wenn nicht, lass es mich wissen ... –

+0

Code ist fixiert, so dass es kompiliert.Schlecken Sie es einfach in eine leere Klasse, und fügen Sie die entsprechenden hinzu Importe. –

7

Für eine begrenzte Anzahl von Ressourcen: Unterklasse SoftReference. Die weiche Referenz sollte auf das umschließende Objekt zeigen. Eine starke Referenz in der Unterklasse sollte auf die Ressource verweisen, daher ist sie immer stark erreichbar. Beim Lesen der ReferenceQueuepoll kann die Ressource geschlossen und aus dem Cache entfernt werden. Der Cache muss korrekt freigegeben werden (wenn eine SoftReference selbst als Garbage Collection erfasst wird, kann sie nicht in eine ReferenceQueue eingereiht werden).

Seien Sie vorsichtig, dass Sie nur eine endliche Anzahl von Ressourcen im Cache nicht freigegeben haben - verwerfen Sie alte Einträge (Sie können die Soft-Referenzen mit if enditable cache verwerfen, wenn dies Ihrer Situation entspricht). Es ist normalerweise der Fall, dass es die Nicht-Speicher-Ressource ist, die wichtiger ist, in welchem ​​Fall ein LRU-Räumungs-Cache ohne exotische Referenzobjekte ausreichend sein sollte.

(Meine Antwort # 1000. Geschrieben von London DevDay.)

+1

Slick. 15 15 15 15 15. –

+1

Ich bin überrascht, es war entfernt kohärent (oder?) Nach einer Stunde oder so Schlaf, ein Tag in einem abgedunkelten Zimmer (mit schlechten Kaffee-Service obwohl arbeiten wifi) und versuchen, einen Lautsprecher zu hören . Aber es musste getan werden. –

+0

Tom, könntest du bitte eine ausführlichere Antwort posten (oder bearbeiten), eventuell begleitet von einem (Pseudo-) Code? Ich hatte auch einen schweren Tag, vielleicht werde ich es morgen besser verstehen, aber momentan scheint es mir leider nicht möglich zu sein. –

2

Ahm.
(Soweit ich weiß) Sie können den Stick nicht von beiden Enden halten. Entweder du hältst an deiner Information fest oder du lässt sie los.
Allerdings ... können Sie einige wichtige Informationen speichern, die es Ihnen ermöglichen, abzuschließen. Natürlich muss die Schlüsselinformation wesentlich kleiner als die "echte Information" sein und darf nicht die reale Information in ihrem erreichbaren Objektgraphen haben (schwache Referenzen können Ihnen dort helfen).
Aufbauend auf dem bestehenden Beispiel (auf die zentrale Informationsfeld bezahlen)

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

Edit:
Ich möchte auf dem erarbeiten „entweder Ihre Informationen halten oder loszulassen“. Angenommen, Sie hatten eine Möglichkeit, an Ihren Informationen festzuhalten. Das hätte den GC gezwungen, die Markierung für Ihre Daten aufzuheben, sodass die Daten erst in einem zweiten GC-Zyklus gelöscht werden, nachdem Sie damit fertig sind. Das ist möglich - und genau dafür ist finalize() da. Da Sie angegeben haben, dass der zweite Zyklus nicht stattfinden soll, können Sie Ihre Information nicht halten (wenn a -> b dann! B ->! A). was bedeutet, dass Sie es gehen lassen müssen.

Edit2:
Eigentlich würde ein zweiter Zyklus auftreten - aber für Ihre "Schlüsseldaten", nicht Ihre "Major Bloat Data". Die tatsächlichen Daten würden im ersten Zyklus gelöscht.

Edit3:
Offensichtlich würde die echte Lösung einen separaten Thread zum Entfernen aus der Referenzwarteschlange verwenden (nicht abfragen(), entfernen(), auf dem dedizierten Thread blockieren).

+0

Vergessen zu erwähnen - das Ausführen dieses Beispiels mit -Xmx 10mb ergibt kein OOM und listet alle Arten von Zahlen auf (angenommen "Schlüsselinformationen"). –

0

@Paul - vielen Dank für die Antwort und Klärung.

@Ran - Ich denke, in Ihrem aktuellen Code i ++ fehlt am Ende der Schleife. Außerdem müssen Sie rq.remove() nicht in der Schleife ausführen, da rq.poll() die oberste Referenz bereits entfernt, oder?

Nur wenige Punkte:

1) hatte ich Thread.sleep (1) Erklärung hinzuzufügen, nachdem ich in der Schleife ++ (für beide Lösungen von Paul und RAN) OOM zu vermeiden, aber das ist irrelevant für das große Bild und ist auch plattformabhängig. Meine Maschine hat eine Quad-Core-CPU und läuft mit Sun Linux 1.6.0_16 JDK.

2) Nachdem ich diese Lösungen angeschaut habe, denke ich, dass ich bei Finalizern bleiben werde. Blochs Buch enthält folgende Gründe:

  • Es gibt keine Garantie, dass Finalizer sofort ausgeführt werden, also niemals etwas Zeitkritisches in einem Finalizer machen - es gibt auch keine Garantien für SoftRererenzen!
  • Nie hängen Sie von einem Finalizer ab, um kritischen anhaltenden Zustand zu aktualisieren - ich bin nicht
  • es gibt eine strenge Leistungseinbuße für die Verwendung von Finalizern - In meinem schlimmsten Fall würde ich über ein einzelnes Objekt pro Minute oder so abschließen. Ich denke, damit kann ich leben.
  • Verwenden Sie versuchen/endlich - oh ja, werde ich auf jeden Fall!
  • Die Notwendigkeit, eine enorme Menge an Gerüst nur für eine scheinbar einfache Aufgabe zu schaffen, erscheint mir nicht vernünftig. Ich meine, wörtlich, die WTF pro Minute Rate wäre ziemlich hoch für jeden anderen Blick auf solchen Code.

    3) Leider gibt es keine Möglichkeit, Punkte zwischen Paul, Tom und Ran zu teilen :( Ich hoffe, Tom würde nichts dagegen haben, da er schon viele davon hatte :) Zwischen Paul und Ran zu urteilen war viel schwieriger - ich denke, dass beide Antworten funktionieren und richtig sind. Ich setze das Akzeptieren-Flag nur auf Pauls Antwort, weil es höher bewertet wurde (und eine detailliertere Erklärung hat), aber Rans Lösung ist überhaupt nicht schlecht und würde wahrscheinlich meine Wahl sein, wenn ich mich dafür entschieden hätte, es mit Hilfe von SoftReferences zu implementieren. Danke Leute!

    +0

    i ++ - ja, hat es wahrscheinlich nicht durch Kopieren/Einfügen geschafft. keine Notwendigkeit zum Entfernen() - korrigieren. Ich vermisse etwa die Hälfte der Referenzen. –