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 }