2010-09-24 14 views
16

Utilizzerò una cache basata su SoftReference (una cosa piuttosto semplice di per sé). Tuttavia, mi sono imbattuto in un problema durante la scrittura di un test per questo.Come rendere la versione di sistema java Soft References?

L'obiettivo del test è verificare se la cache fa richiedere nuovamente l'oggetto memorizzato in precedenza dal server dopo la pulizia della memoria.

Qui trovo il problema di come rendere il sistema in grado di rilasciare oggetti di riferimento morbidi. Chiamare System.gc() non è sufficiente perché i riferimenti software non verranno rilasciati fino a quando la memoria non è in esaurimento. Sto eseguendo questo test unitario sul PC, quindi il budget di memoria per la VM potrebbe essere abbastanza grande.

================== aggiunti in seguito ========================== ====

Grazie a tutti coloro che si sono premurati di rispondere!

Dopo aver considerato tutto di contra pro e ho deciso di andare il modo in cui la forza bruta come consigliato dal Nanda e jarnbjo. È apparso, tuttavia, che JVM non è così stupido - non tenterà nemmeno di raccogliere i rifiuti se chiedete un blocco che da solo è più grande del budget di memoria della VM. Così ho modificato il codice come questo:

/* 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 si sta utilizzando una configurazione di prova molto fragile (e non deterministica?) Per testare la funzionalità di riferimento software Java VM, invece di testare la propria logica dell'applicazione. – Ruben

+0

Sì, sono d'accordo. È stato uno dei contrari per questo approccio. Tuttavia, con questo approccio fai esattamente ciò che il test deve fare - testare il contratto dell'unità. Se avessi preso l'altro modo, testerebbe le parti interne dell'unità, il che è indesiderabile in quanto viola l'incapsulamento e introduce anche una dipendenza non necessaria. Ma hai ragione - nel mio test dipende dal comportamento della VM in caso di crisi della memoria. Nello standard java si dice che VM * rilascerà i soft reference prima di lanciare OutOfMemoryError - beh, speriamo che lo faccia davvero. – JBM

+2

+1, questo mi ha aiutato a eseguire il debug di un problema in cui pensavo che Swing perdesse memoria, ma in realtà conteneva solo riferimenti software ad alcuni oggetti dell'applicazione. – casablanca

risposta

0

è possibile impostare in modo esplicito il riferimento morbido per nulla nel test, e come tale di simulare che il riferimento morbido è stato rilasciato.

Ciò evita qualsiasi configurazione di test complicata che dipende dalla memoria e dalla garbage collection.

1
  1. Impostare il parametro -Xmx su un valore molto piccolo .
  2. Prepara il tuo morbido riferimento
  3. creare il numero di oggetto come possibile. Richiedi l'oggetto ogni volta fino a quando non chiede nuovamente l'oggetto al server.

Questo è il mio piccolo test. Modifica come richiesto.

@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

È possibile impostare -Xmx dall'interno del test? Non ho alcun server di configurazione per eseguire test ... – JBM

+0

In teoria -Xmx non è davvero necessario. Ho suggerito di fare in modo che il test non funzioni per molto tempo. – nanda

-1

Invece di un ciclo lungo in esecuzione (come suggerito da Nanda), probabilmente è più facile e veloce per creare semplicemente una vasta gamma primitiva di allocare più memoria di quella disponibile al VM, poi prendere e ignorare l'OutOfMemoryError:

try { 
     long[] foo = new long[Integer.MAX_VALUE]; 
    } 
    catch(OutOfMemoryError e) { 
     // ignore 
    } 

Questo cancellerà tutti i riferimenti deboli e deboli, a meno che la VM non abbia più di 16 GB di heap disponibile.

+0

che non funziona: Integer.MAX_VALUE è al di fuori della possibile dimensione dell'array su VM. – nanda

+0

@nanda, no, potrebbe benissimo essere "Integer.MAX_VALUE". L'unica restrizione della dimensione è che dovrebbe essere un numero intero e che dovrebbe essere non negativo. – aioobe

+0

Ci sono due problemi con questo approccio: 1. il max. la dimensione dell'array è dipendente da JVM e in genere è di pochi byte inferiore a "Integer.MAX_VALUE', vedere http://stackoverflow.com/q/3038392/2513200 2. La memoria disponibile può essere maggiore di" Integer.MAX_VALUE' – Hulk

13

Questo pezzo di codice forza la JVM a svuotare tutte le SoftReferences. Ed è molto veloce da fare.

Sta funzionando meglio dell'approccio Integer.MAX_VALUE, poiché qui la JVM cerca davvero di allocare quella quantità di memoria.

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

Ora utilizzo questo bit di codice ovunque sia necessario il codice di test dell'unità utilizzando SoftReferences.

Aggiornamento: questo approccio funzionerà effettivamente solo con meno di 2G di memoria massima.

Inoltre, è necessario essere molto attenti con SoftReferences. È così facile mantenere per errore un riferimento difficile che annullerà l'effetto di SoftReferences.

Ecco un semplice test che mostra che funziona ogni volta su OSX. Sarebbe interessato a sapere se il comportamento di JVM è lo stesso su Linux e Windows.


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

Ovviamente non è infallibile se Runtime.getRuntime(). MaxMemory() restituisce un valore al di fuori dell'intervallo positivo di un int. Per esempio. se maxMemory() restituisce un valore compreso tra 2 GB e 4 GB, il cast comporterà il tentativo di creare un array con una dimensione negativa. – jarnbjo

+0

Questo non funziona - almeno perché maxMemory() restituisce l'ammontare di ** byte ** ma stai cercando di allocare ** Oggetti **. L'ho provato nel mio test; pensato che l'OOME sia stato lanciato, la garbage collection non si verifica prima di quello. Ma questo approccio sarà più efficiente dell'allineamento ciclico di piccoli array se allochi i byte invece degli Oggetti e calcoli la dimensione corretta dato il valore maxMemory. – JBM

+1

Hmm, dopo aver provato questo per poche volte ottengo risultati incoerenti - a volte funziona e talvolta non ... Forse http://stackoverflow.com/questions/457229/how-to-cause-soft-references-to -be-clear-in-java/458224 # 458224 potrebbe essere la risposta perché. Se è vero, allora è meglio usare l'allocazione basata su loop. – JBM

2

Un miglioramento che funzionerà per più di 2 G max di memoria. Ciclo finché non si verifica un errore OutOfMemory.

@Test 
public void shouldNotHoldReferencesToObject() { 
    final SoftReference<T> reference = new SoftReference<T>(...); 

    // Sanity check 
    assertThat(reference.get(), not(equalTo(null))); 

    // Force an OoM 
    try { 
     final ArrayList<Object[]> allocations = new ArrayList<Object[]>(); 
     int size; 
     while((size = Math.min(Math.abs((int)Runtime.getRuntime().freeMemory()),Integer.MAX_VALUE))>0) 
      allocations.add(new Object[size]); 
    } catch(OutOfMemoryError e) { 
     // great! 
    } 

    // Verify object has been garbage collected 
    assertThat(reference.get(), equalTo(null)); 

} 
Problemi correlati