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());
// 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());
}
}
}
删除文件失败!原因是没有释放内存。
第二个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);
else
System.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();
// 加上这几行代码,手动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();
}
}
}
则可以保证,成功释放内存
在别的地方也找到类似的处理方法,不过是让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方法来释放内存,所以我们也可以直接在代码里使用以上代码来释放内存。