java significato Come stimare se la JVM ha abbastanza memoria libera per una particolare struttura dati?




memoria heap java (6)

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?


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.

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 servito Quello che abbiamo fatto è ridurre il problema a qualcosa che possiamo migliorare statisticamente verso una soluzione ottimale.


La mia soluzione:

  1. Imposta Xmx come 90%-95% della RAM della macchina fisica se nessun altro processo è in esecuzione tranne il tuo programma. Per la RAM da 32 GB, impostare Xmx 27MB - 28MB Xmx .

  2. Utilizzare uno dei migliori algoritmi gc - CMS o G1GC e mettere a punto i parametri rilevanti. I prefer G1GC if you need more than 4 GB RAM for your application . Fai riferimento a questa domanda se hai scelto G1GC:

    Strategia aggressiva per la raccolta dei rifiuti

    Riduzione del tempo di pausa JVM> 1 secondo utilizzando UseConcMarkSweepGC

  3. Calcola Cap sull'utilizzo della memoria da solo, invece di controllare la memoria libera. Aggiungi memoria utilizzata e memoria da allocare. Subtract it from your own cap like 90% of Xmx . Se la memoria disponibile è ancora disponibile, concedere la richiesta di allocazione di memoria.


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'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.


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.


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.)







out-of-memory