2015-05-04 15 views
11

Ho un'applicazione di elaborazione dati di scala che il 95% delle volte può gestire i dati lanciati in memoria. Il rimanente 5% se lasciato deselezionato di solito non colpisce OutOfMemoryError, ma entra solo in un ciclo di GC principali che picchia la CPU, impedisce l'esecuzione di thread in background e, se lo fa anche, richiede 10x-50x finché quando ha abbastanza memoria.Una metrica utile per determinare quando JVM sta per entrare in memoria/Guasto GC

Ho implementato un sistema in grado di scaricare dati su disco e trattare il flusso del disco come se fosse un iteratore in memoria. Di solito è un ordine di grandezza più lento della memoria, ma sufficiente per questi casi del 5%. Attualmente sto attivando un'euristica della dimensione massima di un contesto di raccolta che tiene traccia delle dimensioni delle varie raccolte coinvolte nell'elaborazione dei dati. Questo funziona, ma in realtà è solo una soglia empirica ad hoc.

Preferirei piuttosto che la JVM si avvicini al cattivo stato sopra riportato e si scarichi su disco in quel momento. Ho provato a guardare la memoria, ma non riesco a trovare la giusta combinazione di eden, vecchi, ecc. Per prevedere in modo affidabile la spirale della morte. Ho anche provato a controllare la frequenza dei principali GC, ma sembra anche che abbia una vasta gamma di "troppo conservatori" in "troppo tardi".

Sarebbe gradita qualsiasi risorsa per giudicare la salute di JVM e rilevare stati problematici.

risposta

2

Un modo affidabile è registrare un listener di notifica sugli eventi GC e controllare lo stato della memoria dopo tutti gli eventi GC completi. Subito dopo un evento GC completo, la memoria utilizzata è il tuo attuale set di dati live. Se a quel punto nel tempo si ha poca memoria libera, è probabile che il tempo inizi a fluttuare sul disco.

In questo modo è possibile evitare i falsi positivi che si verificano spesso quando si tenta di controllare la memoria senza conoscere quando si è verificato un GC completo, ad esempio quando si utilizza il tipo di notifica MEMORY_THRESHOLD_EXCEEDED.

È possibile registrare un listener di notifica e gestire gli eventi GC complete usando qualcosa come il seguente codice:

// ... standard imports ommitted 
import com.sun.management.GarbageCollectionNotificationInfo; 

public static void installGCMonitoring() { 
    List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); 
    for (GarbageCollectorMXBean gcBean : gcBeans) { 
     NotificationEmitter emitter = (NotificationEmitter) gcBean; 
     NotificationListener listener = notificationListener(); 
     emitter.addNotificationListener(listener, null, null); 
    } 
} 

private static NotificationListener notificationListener() { 
    return new NotificationListener() { 
     @Override 
     public void handleNotification(Notification notification, Object handback) { 
      if (notification.getType() 
        .equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { 
       GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo 
         .from((CompositeData) notification.getUserData()); 
       String gctype = info.getGcAction(); 
       if (gctype.contains("major")) { 
        // We are only interested in full (major) GCs 
        Map<String, MemoryUsage> mem = info.getGcInfo().getMemoryUsageAfterGc(); 
        for (Entry<String, MemoryUsage> entry : mem.entrySet()) { 
         String memoryPoolName = entry.getKey(); 
         MemoryUsage memdetail = entry.getValue(); 
         long memMax = memdetail.getMax(); 
         long memUsed = memdetail.getUsed(); 
         // Use the memMax/memUsed of the pool you are interested in (probably old gen) 
         // to determine memory health. 
        } 
       } 
      } 
     } 
    }; 
} 

Cred per this articolo dove siamo arrivati ​​da questa idea.

+0

Ho avuto qualcosa di simile e ho avuto dei problemi perché negli spazi di GC, * Eden * e * Survivor * sarebbe vuoto, distorcendo il vero uso della memoria e vero recupero della memoria. Attualmente sto testando la visione dei principali GC e calcolando il% libero e% recuperato in * Vecchio * e utilizzando quello per impostare una soglia. –

+1

Alla fine, monitorare i principali GC e considerare sia la percentuale di OldGen utilizzata che la percentuale recuperata nel GC si è dimostrata il trigger più affidabile per il mio flushing su disco –

+1

ATTENZIONE: si noti che il metodo "handleNotification" viene chiamato da un nativo codice (chiamando GarbageCollectorImpl.createGCNotification), il che significa che non è possibile eseguire il debug del codice all'interno di questo metodo. Maggiori informazioni qui: https://gist.github.com/rednaxelafx/1465445/5edcf2e1d489ba56077e27bf110090f5b4becde3 – metatechbe

2

potrebbe essere questo link vi aiuterà a http://www.javaspecialists.eu/archive/Issue092.html

Nel mio MemoryWarningSystem si aggiunge ascoltatori che implementano l'interfaccia MemoryWarningSystem.Listener, con un metodo memoryUsageLow(long usedMemory, long maxMemory) che verrà chiamato quando viene raggiunta la soglia. Nei miei esperimenti, il bean di memoria ci avvisa poco dopo che la soglia di utilizzo è stata superata, ma non è stato possibile determinarne la granularità. Qualcosa da notare è che il listener viene chiamato da un thread speciale, chiamato thread Low Memory Detector, che ora fa parte della JVM standard.

Qual è la soglia? E quale delle molte piscine dovremmo monitorare? L'unico pool sensibile da monitorare è la Generazione di possesso (vecchio spazio). Quando si imposta la dimensione della memoria con -Xmx256m, si imposta la memoria massima da utilizzare nella Generalità tenured.

+1

Le risposte costituite esclusivamente da un collegamento esterno non sono risposte valide. Vi suggerisco di citare le parti rilevanti qui e di mantenere il link come riferimento. – m0skit0

+0

Questo articolo sembra suggerire che il vero colpevole è * Vecchio * riempire. Dovrò eseguire alcuni test se * Old * size è un indicatore affidabile del comportamento thrashing che sto vedendo –

+0

Anche con la soglia impostata al 90% di Old, la soglia verrà colpita prima e il GC principale di Ergonomics ripulirà. So Old non è un indicatore affidabile in sé e per sé. Proverò a innescare un GC quando raggiungo la soglia e utilizzerò l'importo recuperato come indicatore del reale utilizzo della memoria, per vedere se si tratta di una misura più affidabile. –

4

Oltre ai meccanismi di notifica MemoryMXBean descritti in @ Alla di link. È possibile utilizzare una combinazione di weak references e code di riferimento. This articolo vecchio ma valido ha una buona descrizione dei riferimenti deboli, deboli e fantasma e delle code di riferimento.

L'idea di base è quella di creare un array di grandi dimensioni (per riservare memoria) creare un riferimento debole o morbido e, in tal caso, aggiungerlo a reference queue. Quando la pressione della memoria innesca la raccolta dell'array debolmente referenziato, otterrete la memoria di riserva (respirando la vita nella vostra applicazione con speranza e dandogli tempo). Avere una discussione che esegue il polling della coda di riferimento per determinare quando è stata raccolta la riserva. È quindi possibile attivare il comportamento di streaming dei file dell'applicazione per completare il lavoro. Le SoftReferences sono più resistenti alla pressione della memoria rispetto a WeakReferences e rendono meglio i tuoi scopi.

+0

Questo è un modo ingegnoso per creare una valvola di sfogo. Dovrò provare e vedere se si innesca al momento opportuno per me –

Problemi correlati