Java Memory-Mapped File所使用的内存分配在物理内存而不是JVM堆内存,且分配在OS内核。
1:
内存映射文件及其应用 - 实现一个简单的消息队列 / 计算机程序的思维逻辑
在一般的文件读写中,会有两次数据拷贝,一次是从硬盘拷贝到操作系统内核,另一次是从操作系统内核拷贝到用户态的应用程序。而在内存映射文件中,一般情况下,只有一次拷贝,且内存分配在操作系统内核,应用程序访问的就是操作系统的内核内存空间,这显然要比普通的读写效率更高。
内存映射文件的另一个重要特点是,它可以被多个不同的应用程序共享,多个程序可以映射同一个文件,映射到同一块内存区域,一个程序对内存的修改,可以让其他程序也看到,这使得它特别适合用于不同应用程序之间的通信。比普通的基于loopback接口的Socket要快10倍。
简单总结下,对于一般的文件读写不需要使用内存映射文件,但如果处理的是大文件,要求极高的读写效率,比如数据库系统或繁忙的电子交易系统,或者需要在不同程序间进行共享和通信,那就可以考虑内存映射文件。
2、
为何要在Java中使用内存映射文件(Memory Mapped File)或者MappedByteBuffer
1). Java语言通过java.nio包支持内存映射文件和IO。
2). 内存映射文件用于对性能要求高的系统中,如繁忙的电子交易系统
3). 使用内存映射IO你可以将文件的一部分加载到内存中
4). 如果被请求的页面不在内存中,内存映射文件会导致页面错误
5). 将一个文件区间映射到内存中的能力取决于内存的可寻址范围。在32位机器中,不能超过4GB,即2^32比特。
6). Java中的内存映射文件比流IO要快(译注:对于大文件而言是对的,小文件则未必)
7). 用于加载文件的内存在Java的堆内存之外,存在于共享内存中,允许两个不同进程访问文件。顺便说一下,这依赖于你用的是direct还是non-direct字节缓存。
8). 读写内存映射文件是操作系统来负责的,因此,即使你的Java程序在写入内存后就挂掉了,只要操作系统工作正常,数据就会写入磁盘。
9). Direct字节缓存比non-direct字节缓存性能要好
10). 不要经常调用MappedByteBuffer.force()方法,这个方法强制操作系统将内存中的内容写入硬盘,所以如果你在每次写内存映射文件后都调用force()方法,你就不能真正从内存映射文件中获益,而是跟disk IO差不多。
11). 如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,意味着可能会丢失一些关键数据。
12). MappedByteBuffer和文件映射在缓存被GC之前都是有效的。sun.misc.Cleaner可能是清除内存映射文件的唯一选择。
3、Java内存映射文件
Java NIO的FileChannel 类提供了一个名为 map( )的方法,该方法可以在一个打开的文件和一个特殊类型的 ByteBuffer 之间建立一个虚拟内存映射,由 map( )方法返回的 MappedByteBuffer 对象的行为类似与基于内存的缓冲区,只不过该对象的数据元素存储在磁盘上的文件中。通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都高。
映射方法: buffer = fileChannel.map(MapMode.READ_WRITE, 0, fileChannel.size());
- 映射模式:MapMode.READ_WRITE、MapMode.READ_ONLY、MapMode.PRIVATE
- 请求的映射模式将受被调用 map( )方法的 FileChannel 对象的访问权限所限制。如:若通道以只读的权限打开的却请求 MapMode.READ_WRITE 模式,则map( )方法会抛出一个 NonWritableChannelException 异常
- MapMode.PRIVATE模式表示一个写时拷贝( copy-on-write)的映射,这意味着通过 put( )方法所做的任何修改都会导致产生一个私有的数据拷贝并且该拷贝中的数据只有MappedByteBuffer 实例可以看到。该过程不会对底层文件做任何修改,而且一旦缓冲区被施以垃圾收集动作( garbage collected),那些修改都会丢失。
通过内存映射文件简单实现持久化消息队列:

1 public static void main(String[] args) throws IOException, InterruptedException { 2 // TODO Auto-generated method stub 3 BasicQueue basicQueue = new BasicQueue("src/cn/edu/buaa/mmap", "mmap_queque"); 4 if (args.length == 1 && args[0].equals("producer")) { 5 Scanner sc = new Scanner(System.in); 6 while (sc.hasNext()) { 7 basicQueue.enqueue(sc.nextLine().getBytes()); 8 } 9 } else { 10 while (true) { 11 byte[] data = basicQueue.dequeue(); 12 if (null != data) { 13 System.out.println(new String(data)); 14 } else { 15 System.out.println(data); 16 } 17 Thread.sleep(1000); 18 } 19 } 20 } 21 22 } 23 24 25 class BasicQueue {// 为简化起见,我们暂不考虑由于并发访问等引起的一致性问题。 26 // 队列最多消息个数,实际个数还会减1 27 private static final int MAX_MSG_NUM = 1024; 28 29 // 消息体最大长度 30 private static final int MAX_MSG_BODY_SIZE = 20; 31 32 // 每条消息占用的空间 33 private static final int MSG_SIZE = MAX_MSG_BODY_SIZE + 4; 34 35 // 队列消息体数据文件大小 36 private static final int DATA_FILE_SIZE = MAX_MSG_NUM * MSG_SIZE; 37 38 // 队列元数据文件大小 (head + tail) 39 private static final int META_SIZE = 8; 40 41 private MappedByteBuffer dataBuf; 42 private MappedByteBuffer metaBuf; 43 44 public BasicQueue(String path, String queueName) throws IOException { 45 if (!path.endsWith(File.separator)) { 46 path += File.separator; 47 } 48 System.out.println(path); 49 RandomAccessFile dataFile = null; 50 RandomAccessFile metaFile = null; 51 try { 52 dataFile = new RandomAccessFile(path + queueName + ".data", "rw"); 53 metaFile = new RandomAccessFile(path + queueName + ".meta", "rw"); 54 55 dataBuf = dataFile.getChannel().map(MapMode.READ_WRITE, 0, DATA_FILE_SIZE); 56 metaBuf = metaFile.getChannel().map(MapMode.READ_WRITE, 0, META_SIZE); 57 } finally { 58 if (dataFile != null) { 59 dataFile.close(); 60 } 61 if (metaFile != null) { 62 metaFile.close(); 63 } 64 } 65 } 66 67 public void enqueue(byte[] data) throws IOException { 68 if (data.length > MAX_MSG_BODY_SIZE) { 69 throw new IllegalArgumentException( 70 "msg size is " + data.length + ", while maximum allowed length is " + MAX_MSG_BODY_SIZE); 71 } 72 if (isFull()) { 73 throw new IllegalStateException("queue is full"); 74 } 75 int tail = tail(); 76 dataBuf.position(tail); 77 dataBuf.putInt(data.length); 78 dataBuf.put(data); 79 80 if (tail + MSG_SIZE >= DATA_FILE_SIZE) { 81 tail(0); 82 } else { 83 tail(tail + MSG_SIZE); 84 } 85 } 86 87 public byte[] dequeue() throws IOException { 88 if (isEmpty()) { 89 return null; 90 } 91 int head = head(); 92 dataBuf.position(head); 93 int length = dataBuf.getInt(); 94 byte[] data = new byte[length]; 95 dataBuf.get(data); 96 97 if (head + MSG_SIZE >= DATA_FILE_SIZE) { 98 head(0); 99 } else { 100 head(head + MSG_SIZE); 101 } 102 return data; 103 } 104 105 private int head() { 106 return metaBuf.getInt(0); 107 } 108 109 private void head(int newHead) { 110 metaBuf.putInt(0, newHead); 111 } 112 113 private int tail() { 114 return metaBuf.getInt(4); 115 } 116 117 private void tail(int newTail) { 118 metaBuf.putInt(4, newTail); 119 } 120 121 private boolean isEmpty() { 122 return head() == tail(); 123 } 124 125 private boolean isFull() { 126 return ((tail() + MSG_SIZE) % DATA_FILE_SIZE) == head(); 127 } 128 }