MappedByteBuffer的映射内存的释放

来源:互联网 发布:qq ubuntu 安装包下载 编辑:程序博客网 时间:2024/06/06 01:09

MappedByteBuffer的内存释放,主要由垃圾回收引起的。

首先,来看一下Oracle的bug list,这是一个无法修复的bug,所以在使用MappedByteBuffer的时候一定要注意内存的释放。

第一个case是:

import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class FileDelete {public static void main(String[] args) {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());// TODOfc.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());}}}
删除文件失败!原因是没有释放内存。

第二个case是:

import java.io.File;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class TestMemoryMapping {public static void main(String[] args) {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(); buffer = null;// System.gc();if (f.delete())System.out.println("Temporary file deleted: " + f);elseSystem.err.println("Not yet deleted: " + f);} catch (Exception ex) {ex.printStackTrace();}}}
将buffer引用设为null,还是失败。我们都知道,垃圾回收器不保证立即回收垃圾,是不可靠的。添加注释掉的代码

System.gc();
会显示的进行垃圾回收,但是同样地,这是没法保证的。我在本机上运行了10000次,失败了五六次。

究其原因,FileChannel在调用了map方法,进行内存映射得到MappedByteBuffer,但是没有提供unmap方法(),释放内存。事实上,unmap方法是在FileChannelImpl类里实现的,是个私有方法。在finalize延迟的时候,unmap方法无法调用,在删除文件的时候就会因为内存未释放而失败。不过可以通过显示的调用unmap方法来释放内存。

import java.io.File;import java.io.RandomAccessFile;import java.lang.reflect.Method;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import sun.nio.ch.FileChannelImpl;public class TestMemoryMapping {public static void main(String[] args) {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();// 加上这几行代码,手动unmapMethod 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);elseSystem.err.println("Not yet deleted: " + f);} catch (Exception ex) {ex.printStackTrace();}}}
则可以保证,成功释放内存

在别的地方也找到类似的处理方法,不过是让buffer本身来释放内存

AccessController.doPrivileged(new PrivilegedAction() {  public Object run() {    try {      Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);      getCleanerMethod.setAccessible(true);      sun.misc.Cleaner cleaner = (sun.misc.Cleaner)       getCleanerMethod.invoke(byteBuffer, new Object[0]);      cleaner.clean();    } catch (Exception e) {      e.printStackTrace();    }    return null;  }});
其实两种方法都是了Cleaner类的,clean方法。

我们从FileChannelImpl的unmap方法来入手

    private static void unmap(MappedByteBuffer bb) {        Cleaner cl = ((DirectBuffer)bb).cleaner();        if (cl != null)            cl.clean();    }
这是一个私有方法,调用了Cleaner的clean方法来释放内存,所以我们也可以直接在代码里使用以上代码来释放内存。








0 0