2010-02-01 8 views
14

Sto lavorando su un server Java che gestisce un sacco di traffico molto denso. Il server accetta pacchetti dai client (spesso molti megabyte) e li inoltra ad altri client. Il server non memorizza mai esplicitamente alcuno dei pacchetti in entrata/in uscita. Tuttavia, il server viene continuamente eseguito nelle eccezioni OutOfMemoryException.Forza l'eliminazione esplicita di un oggetto Java

Ho aggiunto System.gc() nel componente che passa il messaggio del server, sperando che la memoria venga liberata. Inoltre, ho impostato la dimensione heap della JVM su un gigabyte. Sto ancora ricevendo altrettante eccezioni.

Quindi la mia domanda è questa: come posso essere sicuro che i messaggi dei megabyte non vengano accodati indefinitamente (nonostante non siano necessari)? C'è un modo per me di chiamare "cancella" su questi oggetti per garantire che non stiano usando il mio spazio dell'heap?

 try 
     { 
      while (true) 
      { 
       int r = generator.nextInt(100);//generate a random number between 0 and 100 
       Object o =readFromServer.readObject(); 
       sum++; 
       // if the random number is larger than the drop rate, send the object to client, else 
       //it will be dropped 
       if (r > dropRate) 
       { 
        writeToClient.writeObject(o); 
        writeToClient.flush(); 
        numOfSend++; 
        System.out.printf("No. %d send\n",sum); 
       }//if 

      }//while 
     }//try 
+0

Per rispondere ad alcune delle risposte qui il mio codice non memorizza alcun riferimento. Il server è un proxy Socks che passa oggetti. Ho un ciclo while che legge un oggetto da un flusso in entrata e lo scrive su un flusso in uscita. Questo è tutto. Guardando ora gli profiler della memoria. –

+2

leggi molto rapidamente il capitolo "Effective Java";) – Bozho

+0

chiudi quei flussi? Fornisci del codice – Bozho

risposta

3

Guardando il codice: sono le istanze ObjectInput/OutputStream appena create ogni volta che un pacchetto arriva o viene inviato e, in caso affermativo, vengono chiuse correttamente? In caso contrario, chiami reset() dopo ogni lettura/scrittura? Le classi del flusso di oggetti mantengono un riferimento a tutti gli oggetti che hanno visto (per evitare di inviare nuovamente lo stesso oggetto ogni volta che viene inviato), impedendo che vengano raccolti. Ho avuto esattamente questo problema circa 10 anni fa - in realtà la prima volta che ho dovuto usare un profiler per diagnosticare una perdita di memoria ...

+0

Salve .. se posso chiedere, potrebbe essere applicabile alla mia domanda precedente: http://stackoverflow.com/ domande/13488893/java-excel-poi-stops-after-multiple-execution-by-quartz – ides

+0

Il mio programma si arresta ogni volta che sta per scrivere il file excel ... potrebbe il flusso di uscita essere il motivo per cui si sta fermando. Grazie! – ides

+0

@ides: no, non può essere lo stesso problema: il codice crea un nuovo flusso di output e lo chiude in un secondo momento. –

3

Non è possibile chiamare la garbage collection esplicita. Ma questo non è il problema qui. Forse stai memorizzando i riferimenti a questi messaggi. Traccia dove vengono gestiti e assicurati che nessun oggetto tenga riferimento a essi dopo che sono stati utilizzati.

per avere una migliore idea di quello che le migliori pratiche sono, leggere Effective Java, chapter 2 - si tratta di "Creazione e distruzione di oggetti"

+1

È possibile chiamare la garbage collection esplicita, sebbene non sia garantito l'esecuzione. –

+0

quindi non è esplicito. :) – Bozho

+1

Heh, penso che intendesse "È possibile chiamare esplicitamente GC". La chiamata è esplicita, il GC non è ...;) – TMN

11

Quando JVM è su un bordo di OutOfMemoryError, esso sarà eseguire il GC.

Quindi chiamare in anticipo lo System.gc() non risolverà il problema. Il problema deve essere risolto da qualche altra parte. Ci sono fondamentalmente due modi:

  1. Scrivere memoria efficiente codice e/o risolvere perdite di memoria nel codice.
  2. Dare a JVM più memoria.

L'utilizzo di Java Profiler può fornire molte informazioni sull'utilizzo della memoria e potenziali perdite di memoria.

Aggiornamento: secondo la vostra modifica con ulteriori informazioni sul codice di causa questo problema, dare un'occhiata al Geoff Reedy's answer in this topic che suggerisce di utilizzare ObjectInputStream#readUnshared() e ObjectOutputStream#writeUnshared() invece. Anche Javadocs (collegato) lo spiega abbastanza bene.

+3

Personalmente, vorrei riordinare quelli. :) –

+1

@mmyers, 2 è certamente più facile ma se hai una perdita stai solo ritardando leggermente il problema;) – Paolo

+0

@mmyers: done :) – BalusC

2

Non è possibile forzare esplicitamente l'eliminazione, ma è possibile garantire che i riferimenti ai messaggi non vengano mantenuti mantenendo solo un riferimento diretto in memoria e quindi utilizzando gli oggetti di riferimento per contenere i riferimenti raccoglibili.

Cosa ne pensi di utilizzare una coda (di dimensioni ridotte per i messaggi da elaborare), quindi una coda SoftReference secondaria che si alimenta alla prima coda? In questo modo si garantisce che l'elaborazione procederà MA anche che non si verrebbero fuori errori di memoria se i messaggi sono troppo grandi (la coda di riferimento verrà scaricata in quel caso).

1

Se si ottengono eccezioni OutOfMemory, qualcosa mantiene chiaramente un riferimento a questi oggetti. È possibile utilizzare uno strumento come jhat per scoprire dove si trovano questi riferimenti.

2

È possibile tune garbage collection in Java, ma non si può forzare.

1

È necessario sapere se si tratti di oggetti più lunghi del necessario.Il primo passo sarebbe quello di ottenere un profiler sul caso e guardare l'heap e vedere perché gli oggetti non vengono raccolti.

Anche se hai assegnato 1GB JVM, è possibile che la tua giovane generazione sia troppo piccola se molti oggetti vengono creati molto rapidamente costringendoli a generazioni precedenti dove non verranno rimossi altrettanto rapidamente.

Alcune informazioni utili su sintonizzazione GC: http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

4

System.gc() è solo una raccomandazione per la Java Virtual Machine. Si chiama e la JVM può o non può eseguire la garbage collection.

L'OutOfMemoryException può essere causato da due fattori. O si mantengono riferimenti (indesiderati) ai propri oggetti o si accettano molti pacchetti.

Il primo caso può essere analizzato da un profiler, in cui si tenta di scoprire quanti riferimenti sono ancora in diretta. Una buona indicazione per un porro di memoria sta aumentando il consumo di memoria del tuo server. Se ogni richiesta aggiuntiva aumenta il tuo processo Java, è probabile che tu stia conservando dei riferimenti da qualche parte (jconsole potrebbe essere un buon inizio)

Se stai accettando più dati di quanti puoi gestire, dovrai bloccare richieste aggiuntive fino a quando gli altri non saranno completati.

1

il server accetta pacchetti da clienti (spesso molti megabyte) e li inoltra ad altri clienti.

Il codice probabilmente riceve i "pacchetti" completamente prima di inoltrarli. Ciò significa che ha bisogno di memoria sufficiente per archiviare tutti i pacchetti fino a quando non sono stati inoltrati completamente e quando questi pacchetti sono "molti megabyte di grandi dimensioni" significa che hai davvero bisogno di molta memoria. porta anche a una latenza inutile.

È possibile che si verifichi una perdita di memoria, ma se quanto sopra è vero, questo design "store and forward" è il problema più grande. Probabilmente si può ridurre l'utilizzo della memoria del 95% se si riprogetta l'app per non ricevere completamente i pacchetti e invece li si riversa direttamente sui client, cioè si legge solo una piccola parte del pacchetto alla volta e si trasmette immediatamente ai client. Non è difficile farlo in un modo che sembra esattamente lo stesso per i clienti, come quando fai lo store-and-forward.

+0

Normalmente sarei andato con questo design, ma è importante che la mia applicazione rilascia una certa percentuale dei "pacchetti". L'applicazione server proxy socks dovrebbe astrarre l'ambiente di un ambiente di rete ostile. Sto eseguendo schemi di codifica della cancellazione tramite questo proxy (ogni "pacchetto" è un blocco di codici LT). –

+0

@Curious George: Non capisco perché tu * abbia * lasciar cadere i pacchetti a meno che tu non stia scrivendo dei test, ma anche allora, non potresti decidere se lasciar cadere o meno un particolare pacchetto quando arriva, e in quello caso restituire un errore al client di origine o semplicemente esaurire il flusso di input senza memorizzare i dati? –

0

L'attivazione manuale di System.gc non è una buona risposta, come altri hanno pubblicato qui. Non è garantito che funzioni, e innesca un gc completo, che probabilmente bloccherà il tuo server per un lungo periodo di tempo mentre è in esecuzione (> 1 sec se stai dando al tuo server un GB di RAM, ho visto diversi minuti lunghe pause su sistemi più grandi). Potresti sintonizzare il tuo gc che sicuramente ti aiuterà, ma non risolvere completamente il problema.

Se stai leggendo oggetti da un flusso e poi li scrivi su un altro, allora c'è un punto in cui stai tenendo l'intero oggetto in memoria. Se questi oggetti sono, come tu dici, grandi, allora quello potrebbe essere il tuo problema. Prova a riscrivere il tuo IO in modo da leggere byte da 1 stream e scriverli a un altro senza mai tenere esplicitamente l'oggetto completo (anche se non riesco a vedere come funzionerebbe con la serializzazione/deserializzazione degli oggetti se hai bisogno di verificare/validare gli oggetti).

0

solo per aggiungere a tutte quelle risposte precedenti: System.gc() non è un comando per JVM per avviare garbage collection.It è una direzione mite e non garantisce che qualcosa accada. La specifica JVM lascia ai venditori la possibilità di rispondere a ciò che deve essere fatto sulle chiamate gc. I venditori possono anche scegliere di non fare nulla!

+0

FWIW, ogni volta che ho chiamato 'System.gc()' in entrambe le JVM Sun e IBM, lo ha fatto. Non è una chiamata sincrona, la mia ipotesi è che mette semplicemente il thread GC nella coda di esecuzione e restituisce. Il thread GC viene quindi eseguito e si sospende nuovamente. Sì, tecnicamente non è necessario, e certamente non ne dipenderei, ma vale sicuramente la pena provare come passo di debug. Oh, anche i finalizzatori tendono a scappare. Stessi avvertimenti, ma anche le stesse osservazioni. – TMN

+0

Tutto quello che sto dicendo è che non è affidabile. sun e IBM potrebbero averlo implementato. alcuni venditori di x potrebbero non avere .. – Aadith

19

Gli stream di oggetti contengono riferimenti a ogni oggetto scritto/letto da essi. Questo perché il protocollo di serializzazione consente di fare riferimento ai riferimenti agli oggetti visualizzati in precedenza nello stream. Potresti essere in grado di usare ancora questo disegno ma usare writeUnshared/readUnshared invece di writeObject/readObject. Penso, ma non sono sicuro, che ciò impedirà ai flussi di mantenere un riferimento all'oggetto.

Come dice Cowan, anche il metodo reset() è in riproduzione qui. La cosa più sicura da fare è probabilmente usare writeUnshared immediatamente seguito da reset() quando si scrive al ObjectOutputStream s

+1

Non riesci a revocare abbastanza, perché questa è quasi certamente la risposta. Un paio di cose in più: sì, writeUnshared farà esattamente quello che dici. Stranamente, tuttavia, non è garantito da Javadoc, ma l'implementazione di Sun funziona sicuramente come descritto. In secondo luogo, l'altra opzione (specialmente se si desidera un backreference limitato, ovvero si scrive un "cluster" di oggetti che si riferiscono l'un l'altro ma poi il prossimo "cluster" è separato) si usa ObjectOutputStream.reset() tra "batch" , che esplicitamente "ignorano [s] lo stato di qualsiasi oggetto già scritto nel flusso". – Cowan

+0

Ecco un altro +1 per finire definitivamente più in alto della mia risposta :) – BalusC

+0

@BalusC - Grazie;) –

0

Lei parla in modo esplicito bisogno di tutto il pacchetto ricevuto prima di poter inviare? Beh, questo non significa che devi memorizzare tutto in memoria, vero? È una modifica architetturale fattibile per salvare i pacchetti ricevuti su un archivio esterno (forse ram-disk o DB se anche un SSD è troppo lento) e quindi collegarli direttamente al destinatario senza caricarli completamente nella memoria?

0

Se il server viene eseguito per alcuni minuti prima della sua morte, è possibile provare a eseguirlo nella Visual VM. Potresti almeno avere un'idea migliore di quanto velocemente cresce l'heap e di che tipo di oggetti ci sono dentro.

Problemi correlati