filechannel.map 能用于进程间通信是因为多个进程映射同一文件的read_write区域时共享os页缓存,实现伪共享内存;必须用randomaccessfile获取通道,映射前需setlength,写后调用force()并配合状态标志位保障可见性。

FileChannel.map 为什么能用于进程间通信
因为 FileChannel.map 返回的 MappedByteBuffer 本质是将一个磁盘文件的某段区域直接映射到 JVM 进程的虚拟内存中,而操作系统保证:当多个进程对**同一个物理文件**执行 map(且使用 FileChannel.MapMode.READ_WRITE),它们会共享底层的页缓存(page cache)。写入一个进程的映射区,另一个进程能立即读到——前提是文件本身不被截断、不被其他方式覆写,且映射区间一致。
这不是真正的“无文件共享内存”,而是“以文件为媒介的伪共享内存”。它比 Socket 或管道快,是因为绕过了内核态拷贝;但它依赖文件系统,不是 Linux 的 shm_open 或 Windows 的 CreateFileMapping(INVALID_HANDLE_VALUE) 那种纯内存对象。
必须用 RandomAccessFile 获取 FileChannel
用 FileInputStream 或 FileOutputStream 构造的 FileChannel 默认不具备读写双模能力,调用 map(FileChannel.MapMode.READ_WRITE, ...) 会抛 NonReadableChannelException 或 NonWritableChannelException。只有 RandomAccessFile 打开的通道才能安全支持双向映射。
RandomAccessFile raf = new RandomAccessFile("swap.mm", "rw");FileChannel fc = raf.getChannel();- 后续再调用
fc.map(FileChannel.MapMode.READ_WRITE, 0, size) - 别忘了在进程退出前
raf.close(),否则映射可能无法释放或被锁住
同步与可见性问题不能靠 volatile 解决
MappedByteBuffer 是堆外内存,JVM 的内存模型(happens-before)不覆盖它。对 buffer.put(0, (byte)1) 的写操作,另一个进程调用 buffer.get(0) 不一定立刻看到——不是因为指令重排,而是因为 CPU 缓存行未刷新、页缓存未落盘、或 OS 延迟刷回策略。
- 写入后建议调用
buffer.force()强制刷入文件系统(但不保证立即物理落盘) - 更可靠的做法是加一个“就绪标志位”:比如约定第 0 字节为状态码,写入方先写数据,再写状态为
2(可读),读取方轮询该字节直到值变为2 - 避免用
buffer.asCharBuffer()等视图跨进程读写,不同 JVM 字节序或对齐行为可能导致错位
Windows 下需注意 Global 命名空间与权限
Java 层面没有直接创建命名共享内存对象的 API,所以 Java 进程间共享内存只能走文件映射路径。但如果你混合使用 C++/C# 进程和 Java 进程,要注意:Windows 的 CreateFileMapping 若指定名称为 "Global\MyMem",Java 的 FileChannel.map 无法访问它——因为后者映射的是真实磁盘文件,而非内核对象。两者不互通。
若必须跨语言共享,统一走文件路径是最稳妥的:
- 所有进程都映射同一绝对路径的临时文件,如
"C:\temp\shm_12345.mm" - 确保文件提前创建并设好长度(
raf.setLength(size)),否则map可能失败或映射空洞页 - 不要依赖
deleteOnExit(),Windows 下被映射的文件无法删除,得等所有进程 unmap 并 close channel 后手动清理
实际用起来最易忽略的一点:映射大小一旦确定就不能动态扩容,且 map 调用本身不检查文件是否足够大——如果文件当前长度小于请求映射长度,会抛 IOException("Map failed"),错误信息里不会提示具体原因。务必先 setLength,再 map。










