2010-06-01 12 views
9

Ho scritto una classe parser per un particolare formato binario (nfdump se qualcuno è interessato) che usa java.nio's MappedByteBuffer per leggere i file di alcuni GB ciascuno. Il formato binario è solo una serie di intestazioni e soprattutto di record binari di dimensioni fisse, che vengono inviati al chiamante chiamando nextRecord(), che spinge sulla macchina di stato, restituendo null quando viene eseguito. Funziona bene. Funziona su una macchina di sviluppo.Problema di Java map/nio/NFS che causa un errore di VM: "un errore si è verificato in un'operazione di accesso alla memoria recente non sicura nel codice Java compilato"

Sul mio host di produzione, può essere eseguito per alcuni minuti o ore, ma sembra sempre lanciare "java.lang.InternalError: un errore si è verificato in un'operazione di accesso alla memoria recente non sicura in codice Java compilato", digitando uno di i metodi Map.getInt, getShort, ovvero un'operazione di lettura sulla mappa. (?)

Il incontrovertibile codice che imposta la mappa è questo:

/** Set up the map from the given filename and position */ 
    protected void open() throws IOException { 
      // Set up buffer, is this all the flexibility we'll need? 
      channel = new FileInputStream(file).getChannel();  
      MappedByteBuffer map1 = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 
      map1.load(); // we want the whole thing, plus seems to reduce frequency of crashes? 
      map = map1; 
      // assumes the host writing the files is little-endian (x86), ought to be configurable 
      map.order(java.nio.ByteOrder.LITTLE_ENDIAN); 
      map.position(position); 
    } 

e poi usare i vari map.get * metodi per leggere pantaloncini, int, long e altre sequenze di byte, prima di colpendo la fine del file e chiudendo la mappa.

Non ho mai visto l'eccezione generata sul mio host di sviluppo. Ma il punto significativo di differenza tra il mio host di produzione e lo sviluppo è che sul primo, sto leggendo sequenze di questi file su NFS (probabilmente 6-8 TB alla fine, ancora in crescita). Sulla mia macchina di sviluppo, ho una selezione più piccola di questi file a livello locale (60 GB), ma quando esplode nell'host di produzione è in genere ben prima che arrivi a 60 GB di dati.

Entrambe le macchine sono in esecuzione Java 1.6.0_20-b02, anche se l'host di produzione è in esecuzione Debian/Lenny, l'host dev è Ubuntu/karmico. Non sono convinto che farà alcuna differenza. Entrambe le macchine hanno 16 GB di RAM e sono in esecuzione con le stesse impostazioni di heap java.

ritengo che se c'è un bug nel mio codice, c'è abbastanza di un bug nella JVM non a me un'eccezione corretta! Ma penso che sia solo un particolare bug di implementazione di JVM a causa delle interazioni tra NFS e mmap, probabilmente una ricorrenza di 6244515 che è stata riparata ufficialmente.

Ho già provato ad aggiungere una chiamata "carica" ​​per forzare il MappedByteBuffer a caricare il suo contenuto in RAM - questo sembrava ritardare l'errore nell'unica esecuzione di test che ho fatto, ma non impedirlo. Oppure potrebbe essere la coincidenza che è stata la più lunga che è durata prima di schiantarsi!

Se avete letto fino a questo punto e hanno fatto questo genere di cose con java.nio prima, quale sarebbe il tuo istinto essere? In questo momento la mia è di riscriverlo senza nio :)

+0

io ti sto indovinando hai già visto D8 di (http://nfs.sourceforge.net/) – Justin

+0

non avevo, grazie, ma poi non sto scrivendo a questi file uno. –

+0

Sto vedendo questo si verificano con i file mappati in memoria su file system locali ext4 e tmpfs con Java 7u1. –

risposta

4

Vorrei riscriverlo senza utilizzare mappato NIO. Se hai a che fare con più di un file, c'è un problema che la memoria mappata non viene mai rilasciata, quindi esaurisci la memoria virtuale: NB questo non è necessariamente solo un OutOfMemoryError che interagisce con il garbage collector, sarebbe un fallimento nell'assegnazione del nuovo buffer mappato. Vorrei usare un FileChannel.

Detto questo, operazioni su larga scala sui file NFS sono sempre estremamente problematico. Faresti molto meglio a ridisegnare il sistema in modo che ogni file venga letto dalla sua CPU locale. Otterrai anche immensi miglioramenti di velocità in questo modo, molto più del 20% che perderai non utilizzando i buffer mappati.

+0

Ho pensato di perdere lo spazio degli indirizzi virtuali, ma come hai detto tu dovresti manifestare in un errore di mappatura (in più sto leggendo solo un file alla volta, e su un sistema a 64 bit). Probabilmente riorganizzerò i server in modo che i file risiedano sullo stesso server del processo java ed eviterà qualsiasi problema con NFS. A breve termine leggerò tutto in un ByteBuffer, ma poiché più thread stanno leggendo gli stessi file, spesso allo stesso tempo, sta reimplementando cose a cui mmap * dovrebbe * essere una soluzione elegante! –

+0

Sì, speravo in una risposta che mi permetta di tenere mmap, ho solo bisogno di una spinta per qualcun altro per dire "non funzionera '" :) Il codice open() ora appena legge il tutto in un assegnato ByteBuffer. Mentre il mio istinto era di preoccuparsi dello spreco di memoria (come molti lettori = più copie sullo heap), non ho visto un calo delle prestazioni rispetto alle precedenti, quindi non posso davvero lamentarmi. Ho lasciato il vecchio codice commentato, nella speranza che posso ripristinare la mmap "elegante", ma assumendo i miei file nfdump restano della stessa dimensione che probabilmente non avrà bisogno di nuovo. –

+0

'diversi lettori = più copie nell'heap': solo se si eseguono quelle copie multiple. Non puoi organizzare qualche tipo di accesso singleton? – EJP