MappedByteBuffer以及ByteBufer的底层原理
最近在用java中的ByteBuffer,一直不明所以,尤其是對MappedByteBuffer使用的內存映射這個概念云里霧里。
于是首先補了物理內存、虛擬內存、頁面內存、交換區(qū)的知識:小科普——物理內存、頁面文件、交換區(qū)和虛擬內存
然后閱讀了ByteBuffer的文章:ByteBuffer使用和實現以及文件內存映射。
ByteBuffer分為兩種:間接地和直接的,所謂直接就是指MappedByteBuffer,直接使用內存映射(java的話就意味著在JVM之外分配虛擬地址空間);而間接的ByteBuffer是在JVM的堆上面的,說白了就是管理一群byte數組的包裝。
這里面最核心最關鍵的也就是內存映射了,下面的內容來自這篇文件:內存映射文件原理探索
原理
首先,“映射”這個詞,就和數學課上說的“一一映射”是一個意思,就是建立一種一一對應關系,在這里主要是指硬盤上文件的位置與進程邏輯地址空間中一塊大小相同的區(qū)域之間的一一對應,如圖1中過程1所示。這種對應關系純屬是邏輯上的概念,物理上是不存在的,原因是進程的邏輯地址空間本身就是不存在的。在內存映射的過程中,并沒有實際的數據拷貝,文件沒有被載入內存,只是邏輯上被放入了內存,具體到代碼,就是建立并初始化了相關的數據結構(struct address_space),這個過程有系統調用mmap()實現,所以建立內存映射的效率很高。
圖1.內存映射原理
既然建立內存映射沒有進行實際的數據拷貝,那么進程又怎么能最終直接通過內存操作訪問到硬盤上的文件呢?那就要看內存映射之后的幾個相關的過程了。
?
mmap()會返回一個指針ptr,它指向進程邏輯地址空間中的一個地址,這樣以后進程無需再調用read或write對文件進行讀寫,而只需要通過ptr就能夠操作文件。但是ptr所指向的是一個邏輯地址,要操作其中的數據,必須通過MMU將邏輯地址轉換成物理地址,如圖1中過程2所示。這個過程與內存映射無關。
前面講過,建立內存映射并沒有實際拷貝數據,這時,MMU在地址映射表中是無法找到與ptr相對應的物理地址的,也就是MMU失敗,將產生一個缺頁中斷,缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,如果找不到(也就是該文件從來沒有被讀入內存的情況),則會通過mmap()建立的映射關系,從硬盤上將文件讀取到物理內存中,如圖1中過程3所示。這個過程與內存映射無關。
?
如果在拷貝數據時,發(fā)現物理內存不夠用,則會通過虛擬內存機制(swap)將暫時不用的物理頁面交換到硬盤上,如圖1中過程4所示。這個過程也與內存映射無關。
效率
?
從代碼層面上看,從硬盤上將文件讀入內存,都要經過文件系統進行數據拷貝,并且數據拷貝操作是由文件系統和硬件驅動實現的,理論上來說,拷貝數據的效率是一樣的。但是通過內存映射的訪問訪問硬盤上的文件,效率要比read和write系統調用高,這是為什么呢?原因是read()是系統調用,其中進行了數據拷貝到用戶空間,如圖2中過程2,在這個過程中,實際上完成了兩次數據拷貝;而mmap()也是系統調用,如前所述,mmap()中沒有進行數據拷貝,真正的數據拷貝是在缺頁中斷處理時進行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關系,直接將文件從硬盤拷貝到用戶空間,只進行了一次數據拷貝。因此,內存映射的效率要比read/write效率高。
圖2.read系統調用原理
下面這個程序,通過read和mmap兩種方法分別對硬盤上一個名為"mmap_test"的文件進行操作,文件中存有10000個證書,程序兩次使用不同的方法將它們讀出,加1,再寫回硬盤。通過對比可以看出,read消耗的時間將近是mmap的兩到三倍。
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <fcntl.h> #include <sys/mman.h>#define MAX 10000int main() {int i=0;int count=0, fd=0;struct timeval tv1, tv2;int *array = (int *)malloc(sizeof(int)*MAX);/*read*/gettimeofday(&tv1, NULL);fd = open("mman_test", O_RDWR);if (sizeof(int)*MAX != read(fd, (void *)array, sizeof(int)*MAX)) {printf("Reading data filed.../n");return -1;}for (i=0; i<MAX; ++i) {++array[i];}if (sizeof(int)*MAX != write(fd, (void *)array, sizeof(int)*MAX)) {printf("Writing data failed.../n");return -1;}free(array);close(fd);gettimeofday(&tv2, NULL);printf("Time of read/write: %dms\n", tv2.tv_usec-tv1.tv_usec);/*mmap*/gettimeofday(&tv1, NULL);fd = open("mmap_test", O_RDWR);array = mmap(NULL, sizeof(int)*MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);for (i=0; i<MAX; ++i) {++array[i];}munmap(array, sizeof(int)*MAX);msync(array, sizeof(int)*MAX, MS_SYNC);free(array);close(fd);gettimeofday(&tv2, NULL);printf("Time of mmap: %dms\n", tv2.tv_usec - tv1.tv_usec);return 0; }輸出結果:
Time?of?read/write:?154ms
Time?of?mmap:?68ms
總結
以上是生活随笔為你收集整理的MappedByteBuffer以及ByteBufer的底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 进程通信之 mmap
- 下一篇: 干活|常见WAF拦截页面总结