significato - memoria heap java




Come stimare se la JVM ha abbastanza memoria libera per una particolare struttura dati? (5)

Ho la seguente situazione: ci sono un paio di macchine che formano un cluster. I client possono caricare set di dati e dobbiamo selezionare il nodo su cui verrà caricato il set di dati e rifiutare di caricare / evitare un errore OOM se non esiste una macchina che possa adattarsi al set di dati.

Cosa facciamo attualmente: ora il entry count nel set di dati e stima la memory to be used come entry count * empirical factor (determinato manualmente). Quindi controlla se è inferiore alla memoria libera (ottenuta da Runtime.freeMemory() ) e, in tal caso, caricala (altrimenti ripristina il processo su altri nodi / segnala che non c'è capacità disponibile).

I problemi con questo approccio sono:

  • il empirical factor deve essere rivisitato e aggiornato manualmente
  • freeMemory volte freeMemory potrebbe freeMemory a causa di alcuni rifiuti non eliminati (che potrebbero essere evitati eseguendo System.gc prima di ogni chiamata, tuttavia ciò rallenterebbe il sever e potenzialmente potrebbe portare a una promozione prematura)
  • un'alternativa potrebbe essere "provare semplicemente a caricare il set di dati" (e tornare indietro se viene generata una OOM) tuttavia, una volta che viene generata una OOM, è potenzialmente possibile corrompere altri thread in esecuzione nella stessa JVM e non esiste un modo elegante di ripristinarla .

Ci sono soluzioni migliori a questo problema?


Il empirical factor può essere calcolato come step di build e inserito in un file di proprietà.

Mentre freeMemory() è quasi sempre inferiore all'importo che sarebbe gratuito dopo un GC, puoi controllarlo per vedere se è disponibile e chiamare System.gc() se maxMemory() indica che potrebbe esserci un sacco.

NOTA: l'utilizzo di System.gc() in produzione si verifica solo in situazioni molto rare e in genere viene utilizzato in modo errato, con conseguente riduzione delle prestazioni e oscuramento del problema reale.

Eviterei di avviare un OOME a meno che tu non stia correndo una JVM che puoi riavviare come richiesto.


Come hai giustamente notato, l'uso di freeMemory non ti dirà la quantità di memoria che può essere liberata da Java Garbage Collection. È possibile eseguire i test di carico e comprendere il modello di utilizzo dell'heap JVM e l'allocazione della memoria, il modello di de-allocazione utilizzando strumenti come JConsole, VisualVM, jstat e printGCStats su JVM. Ciò darà un'idea sul calcolo del empirical factor più accurato, in pratica capisce qual è il modello di carico che può essere gestito dall'applicazione java. Il prossimo sarebbe scegliere il giusto GC e ottimizzare le impostazioni GC di base per una migliore efficienza. Questa non è una soluzione rapida, ma forse a lungo termine una soluzione migliore.

L'altro modo per uccidere la tua JVM con -XX: OnOutOfMemoryError = "kill -9% p" impostazione JVM, una volta che OOM si verifica, e quindi scrivere, inserire un semplice script di monitoraggio del processo per far apparire la tua JVM se non è attiva.


Un approccio alternativo consiste nell'isolare ogni carico di dati nella propria JVM. È sufficiente predefinire ciascuna dimensione massima dell'heap di JVM e così via e impostare il numero di JVM per host in modo che ciascuna JVM possa occupare tutta la sua dimensione massima dell'heap. Ciò userà un po 'più di risorse - significa che non è possibile utilizzare ogni ultimo byte di memoria inserendo più carichi di dati a bassa memoria - ma semplifica enormemente il problema (e riduce il rischio di sbagliare), Rende possibile dire quando è necessario aggiungere nuovi host e, cosa più importante, riduce l'impatto che un singolo client può avere su tutti gli altri client.

Con questo approccio, una data JVM è "occupata" o "disponibile".

Una volta completato il carico di dati, la JVM pertinente può dichiararsi disponibile per un nuovo carico di dati o semplicemente chiuderla. (In entrambi i casi, ti consigliamo di avere un processo separato per monitorare le JVM e assicurarti che il numero giusto sia sempre in esecuzione.)


I client possono caricare set di dati e dobbiamo selezionare il nodo su cui verrà caricato il set di dati e rifiutare di caricare / evitare un errore OOM se non esiste una macchina che possa adattarsi al set di dati.

Questo è un problema di pianificazione del lavoro, ovvero ho risorse limitate come utilizzarle al meglio. Prenderò il problema di OOM verso la fine.

Abbiamo uno dei fattori principali, ad esempio la RAM, ma le soluzioni ai problemi di pianificazione dipendono da molti fattori, ad esempio ...

  1. I lavori sono piccoli o grandi, vale a dire centinaia / migliaia di questi in esecuzione su un nodo o due o tre. Pensa allo scheduler di Linux.

  2. Devono completare in un determinato periodo di tempo? Programmatore in tempo reale.

Dato tutto ciò che sappiamo all'inizio di un lavoro, possiamo prevedere quando un lavoro terminerà entro un certo lasso di tempo? Se possiamo prevedere che su Node X liberiamo 100 MB ogni 15 - 20 secondi, abbiamo un modo per programmare un lavoro da 200 Mb su quel nodo, ovvero sono sicuro che in 40 secondi avrò completato 200Mb di spazio su quel nodo e il 40 secondi è un limite accettabile per la persona o la macchina che invia il lavoro.

Supponiamo che abbiamo una funzione come segue.

predicted_time predict(long bytes[, factors]); 

I factors sono le altre cose che dovremmo prendere in considerazione che ho menzionato sopra e per ogni applicazione ci saranno cose che puoi aggiungere per adattarle al tuo scenario, cioè quanti fattori spetta a te.

Ai fattori verranno assegnati pesi quando si calcola l' predicted_time .

predicted_time è il numero di millisecondi (può essere qualsiasi TimeUnit) che questo nodo crede da ora che può servire questa attività, il nodo che ti dà il numero più piccolo è il nodo su cui il lavoro dovrebbe essere programmato. È quindi possibile utilizzare questa funzione come segue dove abbiamo una coda di attività, ad esempio, nel codice seguente this.nodes[i] rappresenta un'istanza JVM.

private void scheduleTask() {
  while(WorkEvent()) {
        while(!this.queue.isEmpty()) {
            Task t = this.queue.poll();
            for (int i = 0; i < this.maxNodes; i++) {
                long predicted_time = this.nodes[i].predict(t);
                if (predicted_time < 0) {
                    boolean b = this.queue.offer(t);
                    assert(b);
                    break;
                }
                if (predicted_time <= USER_EXPERIENCE_DELAY) {
                    this.nodes[i].addTask(t);
                    break;
                }
                alert_user(boolean b = this.queue.offer(t);
                assert(b);
            }
        }
    }
}

Se predicted_time < 0 abbiamo un errore, riprogrammiamo il lavoro, in realtà vorremmo sapere perché, ma non è difficile da aggiungere. Se il predicted_time <= USER_EXPERIENCE_DELAY il lavoro può essere pianificato.

Come si evita un OOM

Possiamo raccogliere tutte le statistiche che vogliamo dal nostro schedulatore, ovvero quanti lavori di taglia X, se programmati correttamente, l'obiettivo sarebbe quello di ridurre gli errori e renderli più affidabili nel tempo, ovvero ridurre il numero di volte in cui diciamo al cliente che il loro lavoro non può essere riparato o fallito. Quello che abbiamo fatto o almeno tentiamo di tentare è di ridurre il problema a qualcosa che possiamo migliorare statisticamente verso una soluzione ottimale.


un'alternativa potrebbe essere "provare semplicemente a caricare il set di dati" (e tornare indietro se viene generata una OOM) tuttavia, una volta che viene generata una OOM, è potenzialmente possibile corrompere altri thread in esecuzione nella stessa JVM e non esiste un modo elegante di ripristinarla .

Non ci sono buoni modi per gestire e recuperare da OOME in JVM, ma c'è modo di reagire prima che OOM accada. Java ha java.lang.ref.SoftReference che è garantito che sia stato cancellato prima che la macchina virtuale lanci un OutOfMemoryError . Questo fatto può essere utilizzato per la predizione anticipata di OOM. Ad esempio, il caricamento dei dati può essere interrotto se viene attivata la previsione.

    ReferenceQueue<Object> q = new ReferenceQueue<>();
    SoftReference<Object> reference = new SoftReference<>(new Object(), q);
    q.remove();
    // reference removed - stop data load immediately

La sensibilità può essere regolata con -XX: SoftRefLRUPolicyMSPerMB flag (per Oracle JVM). Soluzione non ideale, l'efficacia dipende da vari fattori: altri riferimenti software utilizzati nel codice, come sono sintonizzati su GC, versione JVM, meteo su Marte ... Ma può essere d'aiuto se si è fortunati.







out-of-memory