<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
java io操作中通常採用BufferedReader,BufferedInputStream等帶緩衝的IO類處理大檔案,不過java nio中引入了一種基於MappedByteBuffer操作大檔案的方式,其讀寫效能極高,本文會介紹其效能如此高的內部實現原理。
在深入MappedByteBuffer之前,先看看計算機記憶體管理的一些知識:
那麼問題來了,為什麼會有虛擬記憶體和實體記憶體的區別?
如果正在執行的一個程序,它所需的記憶體是有可能大於記憶體條容量之和的,如記憶體條是256M,程式卻要建立一個2G的資料區,那麼所有資料不可能都載入到記憶體(實體記憶體),必然有資料要放到其他媒介中(比如硬碟),待程序需要存取那部分資料時,再排程進入實體記憶體。
什麼是虛擬記憶體地址和實體記憶體地址?
假設你的計算機是32位元,那麼它的地址匯流排是32位元的,也就是它可以定址00xFFFFFFFF(4G)的地址空間,但如果你的計算機只有256M的實體記憶體0x0x0FFFFFFF(256M),同時你的程序產生了一個不在這256M地址空間中的地址,那麼計算機該如何處理呢?回答這個問題前,先說明計算機的記憶體分頁機制
計算機會對虛擬記憶體地址空間(32位元為4G)進行分頁產生頁(page),對實體記憶體地址空間(假設256M)進行分頁產生頁幀(page frame),頁和頁幀的大小一樣,所以虛擬記憶體頁的個數勢必要大於實體記憶體頁幀的個數。
在計算機上有一個頁表(page table),就是對映虛擬記憶體頁到實體記憶體頁的,更確切的說是頁號到頁幀號的對映,而且是一對一的對映。
問題來了,虛擬記憶體頁的個數 > 實體記憶體頁幀的個數,豈不是有些虛擬記憶體頁的地址永遠沒有對應的實體記憶體地址空間?
答案肯定是否定的,作業系統是這樣處理的。
作業系統有個頁面失效(page fault)功能。作業系統找到一個最少使用的頁幀,使之失效,並把它寫入磁碟,隨後把需要存取的頁放到頁幀中,並修改頁表中的對映,保證了所有的頁都會被排程。
我們簡單總結一下上面的內容
虛擬記憶體地址:由頁號(與頁表中的頁號關聯)和偏移量(頁的小大,即這個頁能存多少資料)組成。
舉個例子,有一個虛擬地址它的頁號是4,偏移量是20,那麼他的定址過程是這樣的:
首先到頁表中找到頁號4對應的頁幀號(比如為8),如果頁不在記憶體中,則用失效機制調入頁,接著把頁幀號和偏移量傳給MMC組成一個物理上真正存在的地址,最後就是存取實體記憶體的資料了。
從繼承結構上看,MappedByteBuffer繼承自ByteBuffer,內部維護了一個邏輯地址address。
我們通過一個簡單的範例看看MappedByBuffer是怎麼使用的
import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.Scanner; public class MappedByteBufferTest { public static void main(String[] args) { File file = new File("D://data.txt"); long len = file.length(); byte[] ds = new byte[(int) len]; try { MappedByteBuffer mappedByteBuffer = new RandomAccessFile(file, "r") .getChannel() .map(FileChannel.MapMode.READ_ONLY, 0, len); for (int offset = 0; offset < len; offset++) { byte b = mappedByteBuffer.get(); ds[offset] = b; } Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" "); while (scan.hasNext()) { System.out.print(scan.next() + " "); } } catch (IOException e) { } } }
上面程式碼是不是看的不是很明白,我逐一給大家解釋一下
整個程式碼其實就包含了兩個過程:
FileChannel提供了map方法把檔案對映到虛擬記憶體,通常情況可以對映整個檔案,如果檔案比較大,可以進行分段對映。
FileChannel中的幾個變數:
因為MappedByteBuffer做的實在是太過於優秀了,所以我不得不帶著大家讀一下原始碼,看看他們的設計思想和思路
1.通過RandomAccessFile獲取FileChannel
public final FileChannel getChannel() { synchronized (this) { if (channel == null) { channel = FileChannelImpl.open(fd, path, true, rw, this); } return channel; } }
上述實現可以看出,由於synchronized ,只有一個執行緒能夠初始化FileChannel。
2.通過FileChannel.map方法,把檔案對映到虛擬記憶體,並返回邏輯地址address,實現如下:
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { int pagePosition = (int)(position % allocationGranularity); long mapPosition = position - pagePosition; long mapSize = size + pagePosition; try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError x) { System.gc(); try { Thread.sleep(100); } catch (InterruptedException y) { Thread.currentThread().interrupt(); } try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError y) { // After a second OOME, fail throw new IOException("Map failed", y); } } int isize = (int)size; Unmapper um = new Unmapper(addr, mapSize, isize, mfd); if ((!writable) || (imode == MAP_RO)) { return Util.newMappedByteBufferR(isize, addr + pagePosition, mfd, um); } else { return Util.newMappedByteBuffer(isize, addr + pagePosition, mfd, um); } }
上述程式碼可以看出,最終map通過native函數map0完成檔案的對映工作。
static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd, Runnable unmapper) { MappedByteBuffer dbb; if (directByteBufferConstructor == null) initDBBConstructor(); dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance( new Object[] { new Integer(size), new Long(addr), fd, unmapper } return dbb; } // 存取許可權 private static void initDBBConstructor() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { Class<?> cl = Class.forName("java.nio.DirectByteBuffer"); Constructor<?> ctor = cl.getDeclaredConstructor( new Class<?>[] { int.class, long.class, FileDescriptor.class, Runnable.class }); ctor.setAccessible(true); directByteBufferConstructor = ctor; }}); }
由於FileChannelImpl和DirectByteBuffer不在同一個包中,所以有許可權存取問題,通過AccessController類獲取DirectByteBuffer的構造器進行範例化。
DirectByteBuffer是MappedByteBuffer的一個子類,其實現了對記憶體的直接操作。
MappedByteBuffer的get方法最終通過DirectByteBuffer.get方法實現的。
public byte get() { return ((unsafe.getByte(ix(nextGetIndex())))); } public byte get(int i) { return ((unsafe.getByte(ix(checkIndex(i))))); } private long ix(int i) { return address + (i << 0); }
map0()函數返回一個地址address,這樣就無需呼叫read或write方法對檔案進行讀寫,通過address就能夠操作檔案。
底層採用unsafe.getByte方法,通過(address + 偏移量)獲取指定記憶體的資料。
第一次存取address所指向的記憶體區域,導致缺頁中斷,中斷響應函數會在交換區中查詢相對應的頁面,如果找不到(也就是該檔案從來沒有被讀入記憶體的情況),則從硬碟上將檔案指定頁讀取到實體記憶體中(非jvm堆記憶體)。
如果在拷貝資料時,發現實體記憶體不夠用,則會通過虛擬記憶體機制(swap)將暫時不用的物理頁面交換到硬碟的虛擬記憶體中。
從程式碼層面上看,從硬碟上將檔案讀入記憶體,都要經過檔案系統進行資料拷貝,並且資料拷貝操作是由檔案系統和硬體驅動實現的,理論上來說,拷貝資料的效率是一樣的。
但是通過記憶體對映的方法存取硬碟上的檔案,效率要比read和write系統呼叫高,這是為什麼?
read()是系統呼叫,首先將檔案從硬碟拷貝到核心空間的一個緩衝區,再將這些資料拷貝到使用者空間,實際上進行了兩次資料拷貝;
map()也是系統呼叫,但沒有進行資料拷貝,當缺頁中斷髮生時,直接將檔案從硬碟拷貝到使用者空間,只進行了一次資料拷貝。
所以,採用記憶體對映的讀寫效率要比傳統的read/write效能高。
MappedByteBuffer使用虛擬記憶體,因此分配(map)的記憶體大小不受JVM的-Xmx引數限制,但是也是有大小限制的。
如果當檔案超出1.5G限制時,可以通過position引數重新map檔案後面的內容。
MappedByteBuffer在處理大檔案時的確效能很高,但也存在一些問題,如:記憶體佔用、檔案關閉不確定,被其開啟的檔案只有在垃圾回收的才會被關閉,而且這個時間點是不確定的。
javadoc中也提到:
A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.*
翻譯過來便是:
對映的位元組緩衝區及其表示的檔案對映在緩衝區本身被垃圾收集之前保持有效
到此這篇關於深入淺出MappedByteBuffer的文章就介紹到這了,更多相關MappedByteBuffer簡介內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45