Come deselezionare un file dalla memoria mappata usando FileChannel in java?

Sto mappando un file (“sample.txt”) alla memoria usando FileChannel.map() e quindi chiudendo il canale usando fc.close() . Dopodiché, quando scrivo sul file usando FileOutputStream, ricevo il seguente errore:

java.io.FileNotFoundException: sample.txt (L’operazione richiesta non può essere creata su un file con una sezione mappata dall’utente aperta)

 File f = new File("sample.txt"); RandomAccessFile raf = new RandomAccessFile(f,"rw"); FileChannel fc = raf.getChannel(); MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); fc.close(); raf.close(); FileOutputStream fos = new FileOutputStream(f); fos.write(str.getBytes()); fos.close(); 

Presumo che ciò potrebbe essere dovuto al fatto che il file sia ancora mappato alla memoria anche dopo aver chiuso il FileChannel . Ho ragione?. In tal caso, come posso “annullare la mapping” del file dalla memoria? (Non riesco a trovare alcun metodo per questo nell’API). Grazie.

Modifica: Sembra che (aggiungendo un metodo Unmap) sia stato inviato come RFE per riprendersi un po ‘di tempo: http://bugs.sun.com/view_bug.do?bug_id=4724038

Da MappedByteBuffer javadoc:

Un buffer di byte mappato e il mapping del file che rappresenta rimangono validi fino a quando il buffer stesso non viene raccolto.

Prova a chiamare System.gc() ? Anche questo è solo un suggerimento per la VM.

È ansible utilizzare il seguente metodo statico:

 public static void unmap(MappedByteBuffer buffer) { sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner(); cleaner.clean(); } 

Ma questa è una soluzione pericolosa a causa del seguente:
1) portare a guasti se qualcuno usa MappedByteBuffer dopo unmap
2) Si basa sui dettagli di implementazione di MappedByteBuffer

[WinXP, SunJDK1.6] Avevo un ByteBuffer mappato preso da filechannel. Dopo aver letto i messaggi SO finalmente è riuscito a chiamare un addetto alle pulizie attraverso il riflesso senza sole. Non è più necessario il blocco dei file.

 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteBuffer cb = null; try { long size = fc.size(); cb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); ...do the magic... finally { try { fc.close(); } catch (Exception ex) { } try { fis.close(); } catch (Exception ex) { } closeDirectBuffer(cb); } private void closeDirectBuffer(ByteBuffer cb) { if (cb==null || !cb.isDirect()) return; // we could use this type cast and call functions without reflection code, // but static import from sun.* package is risky for non-SUN virtual machine. //try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { } try { Method cleaner = cb.getClass().getMethod("cleaner"); cleaner.setAccessible(true); Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean"); clean.setAccessible(true); clean.invoke(cleaner.invoke(cb)); } catch(Exception ex) { } cb = null; } 

Le idee sono state prese da questi post.
* Come annullare la mapping di un file dalla memoria mappata usando FileChannel in java?
* Esempi di forzare la liberazione della memoria nativa direttamente da ByteBuffer è stato assegnato, usando sun.misc.Unsafe?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40

sun.misc.Cleaner javadoc dice:

Detergenti basati su phantom per scopi generici. I detergenti sono un’alternativa leggera e più robusta alla finalizzazione. Sono leggeri perché non vengono creati dalla VM e quindi non richiedono la creazione di una chiamata JNI upcall e poiché il loro codice di pulizia viene richiamato direttamente dal thread del gestore di riferimento anziché dal thread di finalizzazione. Sono più robusti perché utilizzano riferimenti fantasma, il tipo più debole di object di riferimento, evitando così i problemi di ordinamento insidiosi inerenti alla finalizzazione. Un pulitore tiene traccia di un object referente e incapsula un thunk di codice cleanup arbitrario. Qualche tempo dopo che il GC rileva che il referente di un addetto alle pulizie è diventato raggiungibile dal fantasma, il thread dell’operatore di riferimento eseguirà il programma di pulizia. Gli addetti alle pulizie possono anche essere invocati direttamente; sono thread-safe e assicurano che eseguano i loro thunks al massimo una volta. I detergenti non sostituiscono la finalizzazione. Dovrebbero essere usati solo quando il codice di pulizia è estremamente semplice e diretto. I detergenti non banali sono sconsigliabili poiché rischiano di bloccare il thread dell’operatore di riferimento e ritardare l’ulteriore pulizia e finalizzazione.

Eseguire System.gc () è una soluzione accettabile se la dimensione totale dei buffer è piccola, ma se mappassi gigabyte di file, proverei a implementare in questo modo:

 ((DirectBuffer) buffer).cleaner().clean() 

Ma! Assicurati di non accedere a quel buffer dopo la pulizia o finirai con:

Java Runtime Environment ha rilevato un errore irreversibile: EXCEPTION_ACCESS_VIOLATION (0xc0000005) a pc = 0x0000000002bcf700, pid = 7592, tid = 10184 Versione JRE: Java (TM) SE Runtime Environment (8.0_40-b25) (build 1.8.0_40- b25) Java VM: VM server Java HotSpot (TM) a 64 bit (modalità mista 25.40-b25 windows-amd64 compresso oops) Frame problematico: J 85 C2 java.nio.DirectByteBuffer.get (I) B (16 byte) @ 0x0000000002bcf700 [0x0000000002bcf6c0 + 0x40] Imansible scrivere core dump. I minidump non sono abilitati per impostazione predefinita nelle versioni client di Windows Un file di segnalazione errori con più informazioni viene salvato come: C: \ Users \ ????? \ Programs \ testApp \ hs_err_pid7592.log Metodo compilato (c2) 42392 85 4 java. nio.DirectByteBuffer :: get (16 byte) totale nell’heap [0x0000000002bcf590,0x0000000002bcf828] = 664 riposizionamento [0x0000000002bcf6b0,0x0000000002bcf6c0] = 16 codice principale [0x0000000002bcf6c0,0x0000000002bcf760] = 160 codice stub
[0x0000000002bcf760,0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 metadati
[0x0000000002bcf780,0x0000000002bcf798] = 24 ambiti dati
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 scope di obiettivi
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 dipendenze
[0x0000000002bcf820,0x0000000002bcf828] = 8

In bocca al lupo!

Per aggirare questo bug in Java, ho dovuto fare quanto segue, che funzionerà bene per i file di piccole e medie dimensioni:

  // first open the file for random access RandomAccessFile raf = new RandomAccessFile(file, "r"); // extract a file channel FileChannel channel = raf.getChannel(); // you can memory-map a byte-buffer, but it keeps the file locked //ByteBuffer buf = // channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); // or, since map locks the file... just read the whole file into memory ByteBuffer buf = ByteBuffer.allocate((int)file.length()); int read = channel.read(buf); // .... do something with buf channel.force(false); // doesn't help channel.close(); // doesn't help channel = null; // doesn't help buf = null; // doesn't help raf.close(); // try to make sure that this thing is closed!!!!! 

La memoria mappata viene utilizzata fino a quando non viene liberata dal garbage collector.

Da documenti FileChannel

Una mapping, una volta stabilita, non dipende dal canale file utilizzato per crearlo. La chiusura del canale, in particolare, non ha alcun effetto sulla validità della mapping.

Da MappedByteBuffer java doc

Un buffer di byte mappato e il mapping del file che rappresenta rimangono validi fino a quando il buffer stesso non viene raccolto.

Quindi suggerisco di verificare che non ci siano riferimenti rimanenti al buffer di byte mappato e quindi a richiedere una garbage collection.

Ho trovato informazioni su unmap , è un metodo di FileChannelImpl e non accessibile, quindi puoi invocarlo con java come:

 public static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) { if (buffer == null) { return; } try { Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class); unmap.setAccessible(true); unmap.invoke(channelClass, buffer); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } 

È divertente vedere così tanti consigli per fare ciò che l’Articolo 7 in ‘Effective Java’ dice specificamente di non fare. Un metodo di terminazione come quello che ha fatto @Whome e nessun riferimento al buffer è ciò che è necessario. GC non può essere forzato. Ma questo non impedisce agli sviluppatori di provarci. Un’altra soluzione che ho trovato è stata l’utilizzo di WeakReferences da http://jan.baresovi.cz/dr/en/java#memoryMap

 final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); .... final WeakReference bufferWeakRef = new WeakReference(bb); bb = null; final long startTime = System.currentTimeMillis(); while(null != bufferWeakRef.get()) { if(System.currentTimeMillis() - startTime > 10) // give up return; System.gc(); Thread.yield(); } 

Vorrei provare JNI:

 #ifdef _WIN32 UnmapViewOfFile(env->GetDirectBufferAddress(buffer)); #else munmap(env->GetDirectBufferAddress(buffer), env->GetDirectBufferCapacity(buffer)); #endif 

Include i file: windows.h per Windows, sys / mmap.h per BSD, Linux, OSX.

Se è ansible garantire che l’object buffer di file mappato sia idoneo per la garbage collection, non è necessario GC dell’intera VM per ottenere il rilascio della memoria mappata del buffer. È ansible chiamare System.runFinalization (). Questo chiamerà il metodo finalize () sull’object buffer file mappato (se non ci sono riferimenti nei thread dell’applicazione) che rilascerà la memoria mappata.

La soluzione corretta qui è usare try-with-resources.

Ciò consente la creazione del canale e le altre risorse da esplorare con un blocco. Una volta che il blocco è terminato, il canale e le altre risorse non sono più disponibili e in seguito non possono essere utilizzati (poiché nulla ha un riferimento ad essi).

La mapping della memoria non sarà mai annullata fino alla prossima esecuzione del GC, ma almeno non ci sono riferimenti ciondolanti ad essa.