相關(guān)java IO專題
JAVA IO專題一:java InputStream和OutputStream讀取文件并通過(guò)socket發(fā)送埋哟,到底涉及幾次拷貝
JAVA IO專題二:java NIO讀取文件并通過(guò)socket發(fā)送,最少拷貝了幾次?堆外內(nèi)存和所謂的零拷貝到底是什么關(guān)系
JAVA IO專題三:java的內(nèi)存映射和應(yīng)用場(chǎng)景
JAVA IO專題四:java順序IO原理以及對(duì)應(yīng)的應(yīng)用場(chǎng)景
mmap的作用
mmap的作用,是將文件的一部分直接映射到內(nèi)存(堆外內(nèi)存)漠另,對(duì)這個(gè)映射的操作會(huì)由操作系統(tǒng)在某個(gè)特定的時(shí)期自動(dòng)將臟頁(yè)寫(xiě)回文件對(duì)應(yīng)的位置(也可以通過(guò)msync強(qiáng)制寫(xiě)回)探遵,而不必調(diào)用read/write。mapp完成后僧家,OS并沒(méi)有直接讀取文件的內(nèi)容,而是在真正要訪問(wèn)的時(shí)候裸删,通過(guò)缺頁(yè)異常來(lái)進(jìn)行讀磁盤操作八拱。
mmap相比普通的文件讀寫(xiě)優(yōu)勢(shì)在哪
常規(guī)文件操作為了提高讀寫(xiě)效率和保護(hù)磁盤,使用了頁(yè)緩存機(jī)制涯塔。這樣造成讀文件時(shí)需要先將文件頁(yè)從磁盤拷貝到頁(yè)緩存中肌稻,由于頁(yè)緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址匕荸,所以還需要將頁(yè)緩存中數(shù)據(jù)頁(yè)再次拷貝到內(nèi)存對(duì)應(yīng)的用戶空間中爹谭。
而mmap實(shí)現(xiàn)了將設(shè)備驅(qū)動(dòng)在內(nèi)核空間的部分地址直接映射到用戶空間,使得用戶程序可以直接訪問(wèn)和操作相應(yīng)的內(nèi)容榛搔,減少了額外的拷貝诺凡。
說(shuō)白了,mmap的關(guān)鍵點(diǎn)是實(shí)現(xiàn)了用戶空間和內(nèi)核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過(guò)程践惑。因此mmap效率更高腹泌。
mmap不適合的場(chǎng)景
mmap也不是萬(wàn)能的:
- mmap內(nèi)存映射的大小始終是整數(shù)頁(yè),因此對(duì)于文件實(shí)際大小和映射的空間之間多少回有差異尔觉,這個(gè)差異的空間是被浪費(fèi)的凉袱,對(duì)于小文件來(lái)說(shuō)這個(gè)浪費(fèi)比例被放大,因此mmap更適合大文件侦铜。
- 頻繁映射大量不同大小的內(nèi)存专甩,會(huì)導(dǎo)致內(nèi)存碎片化。
java中mmap的應(yīng)用
java中提供了MappedByteBuffer支持mmap的調(diào)用泵额,其本身是一個(gè)DirectByteBuffer配深,即堆外內(nèi)存携添。獲取MappedByteBuffer方式如下:
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
另外MappedByteBuffer擴(kuò)展了一個(gè)force方法嫁盲,強(qiáng)制將buffer中的內(nèi)容寫(xiě)入磁盤。
java內(nèi)存映射的應(yīng)用場(chǎng)景
消息隊(duì)列的場(chǎng)景非常契合前面說(shuō)到的mmap的優(yōu)勢(shì)烈掠,并且能夠完美避開(kāi)其劣勢(shì)羞秤。java開(kāi)源消息隊(duì)列RocketMQ和QMQ都采用了MappedByteBuffer寫(xiě)消息數(shù)據(jù),以順序?qū)懙姆绞教岣咄掏铝孔蟮小_@里舉QMQ為例(RocketMQ筆者不太了解hhh):
- 所有的消息都用LogSegment來(lái)管理瘾蛋,一個(gè)LogSegment對(duì)應(yīng)磁盤上的一個(gè)文件,持有一個(gè)
fileChannel
和
mappedByteBuffer
矫限,并且每次都會(huì)記錄最后寫(xiě)入的位置AtomicInteger wrotePosition = new AtomicInteger(0);
-
mappedByteBuffer
由channel.map
獲得哺哼,fileSize默認(rèn)都是1G佩抹。 - 寫(xiě)數(shù)據(jù)的時(shí)候會(huì)通過(guò)
mappedByteBuffer.slice
獲取targetByteBuffer
(沒(méi)有直接寫(xiě)到mappedByteBuffer是因?yàn)椋咳《浚┕髌唬⑼ㄟ^(guò)buffer.position(currentPos);
將寫(xiě)指針移動(dòng)到上次寫(xiě)的位置,然后將數(shù)據(jù)追加到后面:
//這里的workingBuffer是堆內(nèi)存茵汰,初始化了一些header數(shù)據(jù)
targetBuffer.put(workingBuffer.array(), 0, headerSize);
//這里的nioBuffer是netty接收到的消息枢里,默認(rèn)為堆外內(nèi)存
targetBuffer.put(message.getBody().nioBuffer());
- 另外在
PeriodicFlushService
中開(kāi)啟一個(gè)定時(shí)任務(wù),每隔一定時(shí)間(默認(rèn)500ms)將buffer數(shù)據(jù)刷盤(通過(guò)buffer.force()
)蹂午。
參考文章
Linux 中 mmap() 函數(shù)的內(nèi)存映射問(wèn)題理解
認(rèn)真分析mmap:是什么 為什么 怎么用