對於MappedByteBuffer映射的文件,直接調用刪除方法是無法刪掉的。原因就是這部分內存的回收靠的是垃圾回收機制。
而垃圾回收的時間是我們無法控制的,這就導致了文件始終被占用。看一個例子:
FileInputStream fis = null; File f = new File("a.txt"); try { fis = new FileInputStream(f); FileChannel fc = fis.getChannel(); // 把文件映射到內存 MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, (int) fc.size()); // TODO fc.close(); fis.close(); } catch (FileNotFoundException ex) { System.err.println("Error! " + ex.getMessage()); System.exit(2); } catch (IOException e) { System.err.println("Error! " + e.getMessage()); System.exit(3); } // 刪除文件 boolean deleted = f.delete(); if (!(deleted)) { System.err.println("Could not delete file " + f.getName()); }
刪除文件失敗!原因是沒有釋放內存。
究其原因,FileChannel在調用了map方法,進行內存映射得到MappedByteBuffer,但是沒有提供unmap方法(),釋放內存。事實上,unmap方法是在FileChannelImpl類里實現的,是個私有方法。在finalize延遲的時候,unmap方法無法調用,在刪除文件的時候就會因為內存未釋放而失敗。不過可以通過顯示的調用unmap方法來釋放內存。
以下代碼則可以保證可以成功刪除文件:
try { File f = File.createTempFile("Test", null); f.deleteOnExit(); RandomAccessFile raf = new RandomAccessFile(f, "rw"); raf.setLength(1024); FileChannel channel = raf.getChannel(); MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, 1024); channel.close(); raf.close(); // 加上這幾行代碼,手動unmap Method m = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class); m.setAccessible(true); m.invoke(FileChannelImpl.class, buffer); if (f.delete()) System.out.println("Temporary file deleted: " + f); else System.err.println("Not yet deleted: " + f); } catch (Exception ex) { ex.printStackTrace(); }
其實最終都是調用了Cleaner類的,clean方法。
我們從FileChannelImpl的unmap方法來入手
private static void unmap(MappedByteBuffer bb) { Cleaner cl = ((DirectBuffer)bb).cleaner(); if (cl != null) cl.clean(); }
這是一個私有方法,調用了Cleaner的clean方法來釋放內存,所以我們也可以直接在代碼里使用以上代碼來釋放內存。