前言 最近在开发个小东西,大量涉及了文件的操作。当然这种事情很简单嘛,最基本的打开个文件流进行写不就行了?
1 2 3 4 FileChannel fc = new RandomAccessFile (file, "rw" ).getChannel();MappedByteBuffer mb = fc.map(FileChannel.MapMode.READ_WRITE, 0 , 4096 ); fc.close()
我本来也是这样想的,可是当你执行这条语句file.delete()
的时候居然返回的是false,删除失败?why?我不是明明已经将文件流关闭了嘛?接下来就是疯狂找原因了。
场景 我们直接看下面的一个简单版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void main (String[] args) { File file = new File (System.getProperty("user.dir" ) + "\\1.txt" ); test(file); System.out.println(file.delete()); }public static void test (File file) { try { FileChannel fc = new RandomAccessFile (file, "rw" ).getChannel(); MappedByteBuffer mb = fc.map(FileChannel.MapMode.READ_WRITE, 0 , 1024 ); byte [] bytes = new byte [1024 ]; Arrays.fill(bytes, (byte ) 1 ); mb.put(bytes); mb.force(); fc.close(); } catch (IOException e) { e.printStackTrace(); } }
可以看到我们是将FileChannel关闭了的,但是将文件进行删除的时候却删除失败了。
思路 然后想到是不是因为MMap也需要close?ok,顺着这个思路,我们进入MappedByteBuffer类。
发现并没有类似close的方法。那就奇怪了,不应该没有啊。然后我们看MMap是FileChannel通过map()获取的,那么在FileChannel中会不会有一个unmap操作的方法?进入FileChannel类
貌似没有看见。没关系,我们进入它的实现类FileChannelImpl中
终于找到了一个看似可以解决问题的方法了!虽然是个private方法,没关系,这种小case,随随便便解决。
1 2 3 Method method = FileChannelImpl.class.getDeclaredMethod("unmap" , MappedByteBuffer.class); method.setAccessible(true ); method.invoke(FileChannelImpl.class, mb);
最后输出true,终于解决了这个问题!
原因 那么为什么会出现这个问题呢?在网上找了许多资料后,发现了这个Bug:JDK-4715154 如果内存使用 FileChannel.map (windows) 映射,则无法删除文件
We cannot fix this. Windows does not allow a mapped file to be deleted. This problem should be ameliorated somewhat once we fix our garbage collectors to deallocate direct buffers more promptly (see 4469299), but otherwise there’s nothing we can do about this.
我们无法解决此问题。Windows 不允许删除映射的文件。一旦我们修复了垃圾回收器以更及时地释放直接缓冲区,这个问题应该会得到一定程度的改善(请参阅4469299),但除此之外,我们对此无能为力。
因为MMap是文件映射的方式将文件映射到虚拟内存中。我们可以推测在调用Filechannel.close()后,MMap映射的内存并没有清除掉,而Windows不允许删除有映射的文件,所以出现了close后,文件并不能删除的情况。
我们在Linux上面执行相同代码,输出为true
解决方法 1. 调用System.gc() 我们在前面推测了是因为映射的内存还没有释放,而在Java中内存的释放是通过垃圾收集器的GC来管理的,那我们是不是手动调用System.gc()
也能将文件进行删除?通过下面的代码,我们可以发现,确实是可以通过System.gc()来释放映射的内存的,但是一般还是不建议显示的调用gc。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main (String[] args) { File file = new File (System.getProperty("user.dir" ) + "\\1.txt" ); test(file); System.gc(); System.out.println(file.delete()); }public static void test (File file) { try { FileChannel fc = new RandomAccessFile (file, "rw" ).getChannel(); MappedByteBuffer mb = fc.map(FileChannel.MapMode.READ_WRITE, 0 , 1024 ); byte [] bytes = new byte [1024 ]; Arrays.fill(bytes, (byte ) 1 ); mb.put(bytes); mb.force(); fc.close(); } catch (Exception e) { e.printStackTrace(); } }
2. 调用FileChannelImpl的unmap() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) { File file = new File (System.getProperty("user.dir" ) + "\\1.txt" ); test(file); System.out.println(file.delete()); }public static void test (File file) { try { FileChannel fc = new RandomAccessFile (file, "rw" ).getChannel(); MappedByteBuffer mb = fc.map(FileChannel.MapMode.READ_WRITE, 0 , 1024 ); byte [] bytes = new byte [1024 ]; Arrays.fill(bytes, (byte ) 1 ); mb.put(bytes); mb.force(); fc.close(); Method method = FileChannelImpl.class.getDeclaredMethod("unmap" , MappedByteBuffer.class); method.setAccessible(true ); method.invoke(FileChannelImpl.class, mb); } catch (Exception e) { e.printStackTrace(); } }
3.Cleaner.clean() 我们仔细观察方法2的内容
1 2 3 4 5 6 private static void unmap (MappedByteBuffer var0) { Cleaner var1 = ((DirectBuffer)var0).cleaner(); if (var1 != null ) { var1.clean(); } }
可以看到unmap方法做的只是将MMap转换为DirectBuffer,获取它的Cleanner,然后调用clean方法。所以我们也可以在代码中添加这段逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void main (String[] args) { File file = new File (System.getProperty("user.dir" ) + "\\1.txt" ); test(file); System.out.println(file.delete()); }public static void test (File file) { try { FileChannel fc = new RandomAccessFile (file, "rw" ).getChannel(); MappedByteBuffer mb = fc.map(FileChannel.MapMode.READ_WRITE, 0 , 1024 ); byte [] bytes = new byte [1024 ]; Arrays.fill(bytes, (byte ) 1 ); mb.put(bytes); mb.force(); fc.close(); Cleaner cleaner = ((DirectBuffer) mb).cleaner(); if (cleaner != null ) { cleaner.clean(); } } catch (Exception e) { e.printStackTrace(); } }