2010-01-14 6 views
13

Sto sviluppando un'applicazione in Java eseguita su dispositivi Windows Mobile. Per raggiungere questo obiettivo, abbiamo utilizzato la JVM Esmertec JBed, che non è perfetta ma per ora ne siamo bloccati. Recentemente abbiamo ricevuto lamentele da parte dei clienti su OutOfMemoryErrors. Dopo un sacco di giochi con le cose ho scoperto che il dispositivo ha un sacco di memoria libera (circa 4 MB).Evitare la frammentazione della memoria durante l'allocazione di molti array in Java

Gli OutOfMemoryErrors si verificano sempre nello stesso punto del codice e cioè quando si espande uno StringBuffer per aggiungervi alcuni caratteri. Dopo aver aggiunto qualche logging in quest'area ho scoperto che il mio StringBuffer conteneva circa 290000 caratteri con una capacità di circa 290500. La strategia di espansione dell'array di caratteri interni è semplicemente raddoppiare le dimensioni, quindi tenterebbe di allocare una matrice di circa 580000 caratteri. Ho stampato l'utilizzo della memoria in questo periodo e ho scoperto che utilizzava circa 3,8 MB di circa 6,8 MB totali (anche se ho visto la memoria totale disponibile salire a circa 12 MB a volte, quindi c'è molto spazio per l'espansione). Quindi è a questo punto che l'applicazione riporta un OutOfMemoryError, che non ha molto senso dato quanto c'è ancora libero.

Ho iniziato a pensare al funzionamento dell'applicazione fino a questo punto. Fondamentalmente quello che sta succedendo è che sto analizzando un file XML usando MinML (un piccolo XML Sax Parser). Uno dei campi nell'XML contiene circa 300k caratteri. Il parser trasmette i dati dal disco e per impostazione predefinita carica solo 256 caratteri alla volta. Quindi, quando raggiunge il campo in questione, il parser chiamerà il metodo "characters()" del gestore più di 1000 volte. Ogni volta che creerà un nuovo carattere [] contenente 256 caratteri. Il gestore aggiunge semplicemente questi caratteri a un StringBuffer. La dimensione iniziale predefinita di StringBuffer è solo 12, così come i caratteri vengono aggiunti al buffer che dovrà crescere un certo numero di volte (ogni volta che si crea un nuovo carattere []). Il mio assunto da questo è che è possibile che mentre c'è abbastanza memoria libera dal momento che il char precedente [] s possa essere garbage collection, forse non c'è un blocco di memoria contiguo abbastanza grande da adattarsi al nuovo array che sto provando a allocare. E forse la JVM non è abbastanza intelligente da espandere la dimensione dell'heap perché è stupida e pensa che non ci sia bisogno perché apparentemente c'è abbastanza memoria libera.

Quindi la mia domanda è: qualcuno ha esperienza di questa JVM e potrebbe essere in grado di confermare in modo conclusivo o confutare le mie supposizioni sull'assegnazione della memoria? E inoltre, qualcuno ha qualche idea (supponendo che le mie supposizioni siano corrette) su come imrove l'allocazione degli array in modo che la memoria non diventi frammentata?

Nota: cose che ho provato già:

  • ho aumentato la dimensione della matrice iniziale del StringBuffer e ho increaed la dimensione di lettura del parser in modo che non avrebbe bisogno di creare tanti array.
  • Ho modificato la strategia di espansione di StringBuffer in modo che, una volta raggiunta una determinata soglia di dimensione, si espandesse solo del 25% anziché del 100%.

Fare entrambe le cose hanno aiutato un po ', ma come ho aumentare la dimensione dei dati XML che vanno in Ho ancora ottenere OutOfMemoryErrors in una dimensione piuttosto basso (circa. 350KB).

Un'altra cosa da aggiungere: tutto questo test è stato eseguito su un dispositivo che utilizzava la JVM in questione. Se eseguo lo stesso codice sul desktop utilizzando Java JVM SE 1.2, non ho alcun problema, o almeno non riesco a ottenere il problema finché i miei dati non raggiungono circa 4 MB di spazio.

EDIT:

un'altra cosa che ho appena provato che ha aiutato un po 'è ho impostato le Xms a 10M. Quindi questo supera il problema della JVM non espandendo l'heap quando dovrebbe e mi consente di elaborare più dati prima che si verifichi l'errore.

risposta

2

Solo per il gusto di aggiornare la mia stessa domanda, ho trovato che la soluzione migliore era impostare la dimensione minima dell'heap (l'ho impostata su 10M).Ciò significa che la JVM non deve mai decidere se espandere o meno l'heap e quindi mai (fino ad ora nel test) muore con un OutOfMemoryError anche se dovrebbe avere molto spazio. Finora in prova siamo riusciti a triplicare la quantità di dati che analizziamo senza errori e potremmo probabilmente andare oltre se dovessimo effettivamente farlo.

Questo è un po 'un trucco per una soluzione rapida per mantenere felici i clienti esistenti, ma ora stiamo guardando una JVM diversa e ti riporto con un aggiornamento se JVM gestisce meglio questo sceglione.

0

Penso che tu abbia un sacco di memoria, ma stai creando un numero enorme di oggetti di riferimento. Prova questo articolo: https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1049545.html?tag=rbxccnbtr1 per ulteriori informazioni.

+0

Sei sicuro? Questo articolo parla di come rendere gli oggetti * più facili * alla raccolta dei rifiuti. –

+0

Non sto creando alcun oggetto di riferimento ?? Come ho già detto, non penso di avere un problema con gli oggetti che non ottengono la raccolta dei dati inutili perché la JVM segnala un sacco di memoria libera. È una questione di dov'è la memoria libera? È frammentato? È per questo che la JVM non può allocare il mio nuovo array? – DaveJohnston

0

Non sono sicuro che questi StringBuffer vengano allocati all'interno di MinML. In caso affermativo, presumo che ne abbiate l'origine? Se lo fai, allora forse mentre stai scansionando una stringa, se la stringa raggiunge una certa lunghezza (ad esempio, 10000 byte), puoi guardare avanti per determinare la lunghezza esatta della stringa e riassegnare un buffer a quella dimensione . Questo è brutto, ma salverebbe la memoria. (Potrebbe anche essere più veloce di non fare la lookaheads, dal momento che si sta potenzialmente salva molte riassegnazioni.)

Se non dispone di accesso alla fonte MinML, allora io non sono sicuro di quello la durata di StringBuffer è relativa al documento XML. Ma questo suggerimento (anche se è ancora più brutto di quello precedente) potrebbe ancora funzionare: dal momento che stai ricevendo l'XML dal disco, forse potresti pre-analizzare usando (per esempio) un parser SAX, solo per ottenere la dimensione della stringa campi e allocare gli StingBuffer di conseguenza?

+0

Gli StringBuffer sono allocati negli oggetti Handler per SaxParser (che in questo caso è MinML). Quindi il gestore in questione alloca un oggetto StringBuffer quindi ogni volta che si chiama il metodo characters() vengono aggiunti altri dati. Non riesco a scansionare una stringa, è tutto in streaming dal file, quindi non riesco a scoprire la dimensione della stringa finale in anticipo a meno che non faccia come hai detto nel secondo suggerimento e analizzo il file due volte. Ma come hai detto è brutto e richiede tempo. – DaveJohnston

+0

Brutto, sì. Ma potrebbe essere più veloce di quanto ti aspetteresti, specialmente se il tuo metodo attuale richiede molte ridistribuzioni. –

0

Sei in grado di ottenere un dump dell'heap dal dispositivo?

Se si ottiene il dump dell'heap ed è in un formato compatibile, alcuni analizzatori di memoria Java forniscono informazioni sulla dimensione dei blocchi di memoria contigui. Ho Ricorda vedendo questa funzionalità in IBM Heap Analyzer http://www.alphaworks.ibm.com/tech/heapanalyzer, ma anche controllare il più aggiornato Eclipse Memory Analyzer http://www.eclipse.org/mat/

Se avete la possiblity di modificare il file XML, che sarebbe probabilmente il modo più rapido fuori. L'analisi XML in Java è sempre piuttosto intensiva di memoria e 300K è abbastanza per un singolo campo. Invece potresti provare a separare questo campo in un file non xml separato.

+0

Dubito molto che sarei in grado di ottenere un dump dell'heap, la JVM è molto limitata in quello che puoi fare con esso, o almeno non è ben documentata, quindi non saprei come farlo. La modifica dell'XML è una possibilità che stiamo considerando come ultima risorsa perché l'XML è un insieme di risultati di ricerca restituiti da un server. Cambiarlo significherebbe apportare modifiche alla nostra struttura server semplicemente per risolvere quello che sembra un problema con la JVM. Se è così, va bene, ma speriamo di trovare un modo per far funzionare correttamente la JVM. – DaveJohnston

1

Da quello che so su JVM, la frammentazione non dovrebbe mai essere un problema per voi per risolvere. Se non c'è più spazio per l'allocazione, a causa della frammentazione o meno, il garbage collector deve essere eseguito e i GC tipicamente comprimono anche i dati per risolvere i problemi di frammentazione.

Per enfatizzare - si ricevono solo errori di "memoria insufficiente" dopo il GC è stato eseguito e non è possibile liberare memoria sufficiente.

Vorrei provare a scavare di più nelle opzioni per la JVM specifica che stai utilizzando. Ad esempio, un garbage collector di "copia" usa solo la metà della memoria disponibile alla volta, quindi cambiare la tua VM per usare qualcos'altro potrebbe liberare metà della tua memoria.

Non sto davvero suggerendo che la vostra VM usi la semplice copia di GC, sto solo suggerendo di verificarla a livello di VM.

+0

Il supporto per la JVM che sto usando è sfortunatamente inesistente (a meno che qualcuno non conosca un buon posto per ottenere supporto per Esmertec JBed CDC ??). Qualche idea su quali sono le opzioni standard della riga di comando per cambiare le opzioni GC? – DaveJohnston

+0

@DaveJohnston: puoi controllare la documentazione delle JVM più diffuse e sperare che le tue si comportino allo stesso modo; ma non esiste uno standard definito dalle specifiche VM di Java (infatti, afferma esplicitamente: "il layout di memoria delle aree di dati di run-time, l'algoritmo di garbage-collection utilizzato [...] sono lasciati alla discrezione dell'attore"). – Oak

2

Forse potresti provare la luce VTD. Sembra più efficiente in termini di memoria rispetto a SAX. (So ​​che è un enorme cambiamento.)

Problemi correlati