2010-09-24 5 views
16

Ich verwende einen SoftReference-basierten Cache (eine ziemlich einfache Sache an sich). Allerdings habe ich ein Problem beim Schreiben eines Tests dafür gefunden.Wie macht das Java-System Soft References frei?

Das Ziel des Tests besteht darin, zu überprüfen, ob der Cache das zuvor zwischengespeicherte Objekt erneut vom Server anfordert, nachdem die Speicherbereinigung durchgeführt wurde.

Hier finde ich das Problem, wie das System weiche referenzierte Objekte freigeben kann. Das Aufrufen von System.gc() reicht nicht aus, da weiche Referenzen erst freigegeben werden, wenn der Arbeitsspeicher niedrig ist. Ich führe diesen Komponententest auf dem PC durch, so dass das Speicherbudget für die VM ziemlich groß sein könnte. später

================== Hinzugefügt ========================== ====

Vielen Dank an alle, die darauf geachtet haben, zu antworten!

Schließlich Pro und Contra des bedenkt, dass ich die Brute-Force-Weg zu gehen, entschieden haben, wie von nanda und Jarnbjo beraten. Es scheint jedoch, dass JVM nicht so dumm ist - es wird nicht einmal Müll sammeln, wenn man nach einem Block fragt, der allein größer ist als das Speicherbudget von VM. Also habe ich den Code wie folgt geändert:

/* Force releasing SoftReferences */ 
    try { 
     final List<long[]> memhog = new LinkedList<long[]>(); 
     while(true) { 
      memhog.add(new long[102400]); 
     } 
    } 
    catch(final OutOfMemoryError e) { 
     /* At this point all SoftReferences have been released - GUARANTEED. */ 
    } 

    /* continue the test here */ 
+0

IMHO verwenden Sie eine sehr spröde (und nicht deterministische?) Testkonfiguration, um die Java VM Soft Reference Funktionalität zu testen, anstatt Ihre eigene Anwendungslogik zu testen. – Ruben

+0

Ja, ich stimme zu. Es war einer der Kontra für diesen Ansatz. Mit diesem Ansatz machen Sie jedoch genau das, was der Test tun muss - testen Sie den Vertrag des Geräts. Wenn ich den anderen Weg nehmen würde, würde es die Interna der Einheit testen - was unerwünscht ist, da es die Kapselung verletzt, und führt auch unnötige Abhängigkeit ein. Aber Sie haben Recht - in meinem Test hängt es vom Verhalten der VM im Falle einer Speicherkrise ab. Im Java-Standard heißt es, dass VM * Soft-Referenzen freigibt, bevor OutOfMemoryError geworfen wird - nun, hoffen wir, dass es tatsächlich so ist. – JBM

+2

+1, dies half mir, ein Problem zu debuggen, bei dem ich dachte, Swing habe Speicher verloren, aber es war wirklich nur weiche Referenzen auf einige Anwendungsobjekte. – casablanca

Antwort

0

Sie explizit den weichen Bezug auf null in Ihrem Test einstellen könnte, und als solche simulieren, dass der weiche Bezug freigegeben wurde.

Dies vermeidet jede komplizierte Testkonfiguration, die von Speicher und Speicherbereinigung abhängig ist.

1
  1. Den Parameter -Xmx auf einen sehr kleinen Wert von setzen.
  2. Bereiten Sie Ihre weiche Referenz
  3. Erstellen Sie so viele Objekte wie möglich. Fragen Sie jedes Mal nach dem Objekt, bis es das Objekt erneut vom Server angefordert hat.

Dies ist mein kleiner Test. Ändern Sie es nach Bedarf.

@Test 
public void testSoftReference() throws Exception { 
    Set<Object[]> s = new HashSet<Object[]>(); 

    SoftReference<Object> sr = new SoftReference<Object>(new Object()); 

    int i = 0; 

    while (true) { 
     try { 
      s.add(new Object[1000]); 
     } catch (OutOfMemoryError e) { 
      // ignore 
     } 
     if (sr.get() == null) { 
      System.out.println("Soft reference is cleared. Success!"); 
      break; 
     } 
     i++; 
     System.out.println("Soft reference is not yet cleared. Iteration " + i); 
    } 
} 
+0

Ist es möglich, -Xmx innerhalb des Tests zu setzen? Ich habe keinen Konfigurationsserver zum Ausführen von Tests ... – JBM

+0

In der Theorie -Xmx ist nicht wirklich erforderlich. Ich habe das vorgeschlagen, damit der Test nicht lange dauert. – nanda

-1

Statt einer langen Laufschleife (wie von nanda vorgeschlagen), ist es wahrscheinlich schneller und einfacher, einfach eine riesige primitive Array zu erstellen, um mehr Speicher zuzuteilen als verfügbar an die VM, dann fangen und die OutOfMemoryError ignorieren:

Dadurch werden alle schwachen und weichen Referenzen gelöscht, es sei denn, Ihre VM verfügt über mehr als 16 GB verfügbaren Heapspeicher.

+0

, die nicht funktioniert: Integer.MAX_VALUE ist außerhalb der möglichen Array-Größe auf VM. – nanda

+0

@nanda, nein, es kann sehr gut 'Integer.MAX_VALUE' sein. Die einzige Einschränkung der Größe ist, dass es eine ganze Zahl sein sollte und dass sie nicht negativ sein sollte. – aioobe

+0

Es gibt zwei Probleme mit diesem Ansatz: 1. die max. Array-Größe ist JVM abhängig und ist normalerweise ein paar Bytes weniger als 'Integer.MAX_VALUE', siehe http://StackOverflow.com/q/3038392/2513200 2. Der verfügbare Speicher kann größer sein als' Integer.MAX_VALUE' – Hulk

13

Dieser Codeabschnitt zwingt die JVM, alle SoftReferences zu leeren. Und es ist sehr schnell zu tun.

Es funktioniert besser als der Integer.MAX_VALUE Ansatz, da hier die JVM wirklich versucht, so viel Speicher zuzuweisen.

try { 
    Object[] ignored = new Object[(int) Runtime.getRuntime().maxMemory()]; 
} catch (OutOfMemoryError e) { 
    // Ignore 
}

Ich benutze jetzt dieses Bit des Codes überall wo ich Testcode mit SoftReferences einheiten muss.

Update: Dieser Ansatz wird in der Tat nur mit weniger als 2G maximalen Speicher arbeiten.

Auch muss man mit SoftReferences sehr vorsichtig sein. Es ist so einfach, versehentlich einen harten Verweis zu behalten, der den Effekt von SoftReferences negiert.

Hier ist ein einfacher Test, der zeigt, dass es jedes Mal auf OSX funktioniert. Wäre daran interessiert zu wissen, ob das Verhalten von JVM unter Linux und Windows das gleiche ist.


for (int i = 0; i < 1000; i++) { 
    SoftReference<Object> softReference = new SoftReferencelt<Object>(new Object()); 
    if (null == softReference.get()) { 
     throw new IllegalStateException("Reference should NOT be null"); 
    } 

    try { 
     Object[] ignored = new Object[(int) Runtime.getRuntime().maxMemory()]; 
    } catch (OutOfMemoryError e) { 
     // Ignore 
    } 

    if (null != softReference.get()) { 
     throw new IllegalStateException("Reference should be null"); 
    } 

    System.out.println("It worked!"); 
}
+0

Das ist natürlich nicht narrensicher, wenn Runtime.getRuntime(). MaxMemory() einen Wert außerhalb des positiven Bereichs eines int zurückgibt. Z.B. Wenn maxMemory() einen Wert zwischen 2 GB und 4 GB zurückgibt, führt der Cast dazu, dass ein Array mit einer negativen Größe erstellt wird. – jarnbjo

+0

Dies funktioniert nicht - zumindest, weil maxMemory() eine Menge von ** Bytes ** zurückgibt, aber Sie versuchen, ** Objekte ** zuzuordnen. Ich habe es in meinem Test versucht; dachte das OOME geworfen wird, die Garbage-Sammlung tritt nicht zuvor auf. Dieser Ansatz ist jedoch effizienter als die Zuweisung kleiner Arrays durch Schleifen, wenn Sie Byte anstelle von Objekten zuweisen und die richtige Größe für den maxMemory-Wert berechnen. – JBM

+1

Hmm, nachdem ich dies einige Male versucht habe, bekomme ich inkonsistente Ergebnisse - manchmal funktioniert es und manchmal nicht ... Vielleicht http://StackOverflow.com/Questions/457229/How-to-Cause-Soft-References-to -be-cleared-in-java/458224 # 458224 könnte die Antwort sein, warum. Wenn es wahr ist, ist es besser, die loopbasierte Zuweisung zu verwenden. – JBM

2

Eine Verbesserung, die für mehr als 2G maximalen Speicher funktioniert. Es wird wiederholt, bis ein OutOfMemory-Fehler auftritt.