使用 mappedbytebuffer 的核心做法是调用 filechannel.map() 方法将文件区域映射到堆外内存,随后像操作普通字节数组一样进行读写。你需要指定映射模式(只读、读写或私有),并处理可能抛出的 ioexception 和 outofmemoryerror。由于它不遵循标准的 closeable 接口释放资源,必须通过反射调用 sun.misc.cleaner 或在 java 9+ 中调用 force 后依赖 gc,但推荐显式清理以避免内存泄漏。

如何创建 MappedByteBuffer 并设置访问模式
创建映射的关键在于获取一个非关闭状态的 FileChannel 并调用 map() 方法。该方法接收三个参数:映射模式 MapMode、起始位置 position 和映射大小 size。常用的模式包括 READ_ONLY 用于安全读取,READ_WRITE 允许修改文件内容,以及 PRIVATE 用于创建文件的私有副本,修改不会影响原文件。如果指定的范围超出文件大小,会抛出 IllegalArgumentException。

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MapExample {
public static void main(String[] args) throws Exception {
// 1. 打开文件通道
RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");
FileChannel channel = raf.getChannel();
// 2. 映射整个文件到内存,模式为读写
long fileSize = channel.size();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, fileSize);
// 3. 写入数据
buffer.put(0, (byte) 'A');
buffer.putInt(4, 12345);
// 4. 确保更改同步到磁盘
buffer.force();
// 5. 关闭资源
channel.close();
raf.close();
}
}
上述代码展示了完整的生命周期。首先通过 RandomAccessFile 获取 FileChannel,然后使用 map(MapMode.READ_WRITE, 0, fileSize) 建立映射。写入操作直接修改缓冲区内容,最后必须调用 force() 将未保存的更改刷入磁盘。注意 FileChannel 和 RandomAccessFile 都需要手动关闭,而 MappedByteBuffer 本身没有 close() 方法。
如何处理内存溢出与资源清理问题
使用 MappedByteBuffer 时最大的风险是 OutOfMemoryError,因为映射占用的是进程虚拟地址空间而非堆内存。当映射大文件或频繁创建映射而不释放时,容易耗尽地址空间。此外,Java 8 及之前版本缺乏直接的清理机制,需借助反射调用内部类 sun.misc.Cleaner 来解除映射。Java 9 引入了 MappedByteBuffer.cleaner() 方法,但官方仍建议谨慎使用。

import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
public class CleanerUtil {
public static void clean(final MappedByteBuffer buffer) {
if (buffer == null) return;
// 尝试获取 cleaner
Object cleaner = null;
try {
Method getCleaner = buffer.getClass().getMethod("cleaner");
cleaner = getCleaner.invoke(buffer);
} catch (Exception e) {
// Java 8 及以下,通过 sun.misc.Cleaner
try {
Method cleanerMethod = buffer.getClass().getDeclaredMethod("cleaner");
cleanerMethod.setAccessible(true);
cleaner = cleanerMethod.invoke(buffer);
} catch (Exception ex) {
// 如果无法获取,只能依赖 GC,但可能泄漏
System.err.println("Cannot clean buffer directly");
return;
}
}
if (cleaner != null && cleaner instanceof sun.misc.Cleaner) {
((sun.misc.Cleaner) cleaner).clean();
}
}
}
在需要处理大量文件映射的场景中,应在不再需要映射时立即调用清理逻辑。上面的工具类演示了如何通过反射获取并调用 Cleaner 的 clean() 方法。对于 Java 9+,可以直接调用 buffer.cleaner().clean()。务必在 try-finally 块中执行此操作,确保即使发生异常也能释放系统资源。同时,避免在循环中无限制地创建新的映射,应考虑复用缓冲区或使用其他 I/O 策略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











