0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

汽車玩家 ? 來(lái)源:IT知識(shí)課堂 ? 作者:IT知識(shí)課堂 ? 2020-05-05 23:42 ? 次閱讀

java io操作中通常采用BufferedReader,BufferedInputStream等帶緩沖的IO類處理大文件,不過(guò)java nio中引入了一種基于MappedByteBuffer操作大文件的方式,其讀寫性能極高,本文會(huì)介紹其性能如此高的內(nèi)部實(shí)現(xiàn)原理。

內(nèi)存管理

在深入MappedByteBuffer之前,先看看計(jì)算機(jī)內(nèi)存管理的幾個(gè)術(shù)語(yǔ):

MMC:CPU的內(nèi)存管理單元。

物理內(nèi)存:即內(nèi)存條的內(nèi)存空間。

虛擬內(nèi)存:計(jì)算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù)。它使得應(yīng)用程序認(rèn)為它擁有連續(xù)的可用的內(nèi)存(一個(gè)連續(xù)完整的地址空間),而實(shí)際上,它通常是被分隔成多個(gè)物理內(nèi)存碎片,還有部分暫時(shí)存儲(chǔ)在外部磁盤存儲(chǔ)器上,在需要時(shí)進(jìn)行數(shù)據(jù)交換。

頁(yè)面文件:操作系統(tǒng)反映構(gòu)建并使用虛擬內(nèi)存的硬盤空間大小而創(chuàng)建的文件,在windows下,即pagefile.sys文件,其存在意味著物理內(nèi)存被占滿后,將暫時(shí)不用的數(shù)據(jù)移動(dòng)到硬盤上。

缺頁(yè)中斷:當(dāng)程序試圖訪問(wèn)已映射在虛擬地址空間中但未被加載至物理內(nèi)存的一個(gè)分頁(yè)時(shí),由MMC發(fā)出的中斷。如果操作系統(tǒng)判斷此次訪問(wèn)是有效的,則嘗試將相關(guān)的頁(yè)從虛擬內(nèi)存文件中載入物理內(nèi)存。

為什么會(huì)有虛擬內(nèi)存和物理內(nèi)存的區(qū)別?
如果正在運(yùn)行的一個(gè)進(jìn)程,它所需的內(nèi)存是有可能大于內(nèi)存條容量之和的,如內(nèi)存條是256M,程序卻要?jiǎng)?chuàng)建一個(gè)2G的數(shù)據(jù)區(qū),那么所有數(shù)據(jù)不可能都加載到內(nèi)存(物理內(nèi)存),必然有數(shù)據(jù)要放到其他介質(zhì)中(比如硬盤),待進(jìn)程需要訪問(wèn)那部分?jǐn)?shù)據(jù)時(shí),再調(diào)度進(jìn)入物理內(nèi)存。

什么是虛擬內(nèi)存地址和物理內(nèi)存地址?
假設(shè)你的計(jì)算機(jī)是32位,那么它的地址總線是32位的,也就是它可以尋址00xFFFFFFFF(4G)的地址空間,但如果你的計(jì)算機(jī)只有256M的物理內(nèi)存0x0x0FFFFFFF(256M),同時(shí)你的進(jìn)程產(chǎn)生了一個(gè)不在這256M地址空間中的地址,那么計(jì)算機(jī)該如何處理呢?回答這個(gè)問(wèn)題前,先說(shuō)明計(jì)算機(jī)的內(nèi)存分頁(yè)機(jī)制。

計(jì)算機(jī)會(huì)對(duì)虛擬內(nèi)存地址空間(32位為4G)進(jìn)行分頁(yè)產(chǎn)生頁(yè)(page),對(duì)物理內(nèi)存地址空間(假設(shè)256M)進(jìn)行分頁(yè)產(chǎn)生頁(yè)幀(page frame),頁(yè)和頁(yè)幀的大小一樣,所以虛擬內(nèi)存頁(yè)的個(gè)數(shù)勢(shì)必要大于物理內(nèi)存頁(yè)幀的個(gè)數(shù)。在計(jì)算機(jī)上有一個(gè)頁(yè)表(page table),就是映射虛擬內(nèi)存頁(yè)到物理內(nèi)存頁(yè)的,更確切的說(shuō)是頁(yè)號(hào)到頁(yè)幀號(hào)的映射,而且是一對(duì)一的映射。
問(wèn)題來(lái)了,虛擬內(nèi)存頁(yè)的個(gè)數(shù) > 物理內(nèi)存頁(yè)幀的個(gè)數(shù),豈不是有些虛擬內(nèi)存頁(yè)的地址永遠(yuǎn)沒有對(duì)應(yīng)的物理內(nèi)存地址空間?不是的,操作系統(tǒng)是這樣處理的。操作系統(tǒng)有個(gè)頁(yè)面失效(page fault)功能。操作系統(tǒng)找到一個(gè)最少使用的頁(yè)幀,使之失效,并把它寫入磁盤,隨后把需要訪問(wèn)的頁(yè)放到頁(yè)幀中,并修改頁(yè)表中的映射,保證了所有的頁(yè)都會(huì)被調(diào)度。

現(xiàn)在來(lái)看看什么是虛擬內(nèi)存地址和物理內(nèi)存地址:

虛擬內(nèi)存地址:由頁(yè)號(hào)(與頁(yè)表中的頁(yè)號(hào)關(guān)聯(lián))和偏移量(頁(yè)的小大,即這個(gè)頁(yè)能存多少數(shù)據(jù))組成。

舉個(gè)例子,有一個(gè)虛擬地址它的頁(yè)號(hào)是4,偏移量是20,那么他的尋址過(guò)程是這樣的:首先到頁(yè)表中找到頁(yè)號(hào)4對(duì)應(yīng)的頁(yè)幀號(hào)(比如為8),如果頁(yè)不在內(nèi)存中,則用失效機(jī)制調(diào)入頁(yè),接著把頁(yè)幀號(hào)和偏移量傳給MMC組成一個(gè)物理上真正存在的地址,最后就是訪問(wèn)物理內(nèi)存的數(shù)據(jù)了。

MappedByteBuffer是什么

從繼承結(jié)構(gòu)上看,MappedByteBuffer繼承自ByteBuffer,內(nèi)部維護(hù)了一個(gè)邏輯地址address。

示例

通過(guò)MappedByteBuffer讀取文件

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

map過(guò)程

FileChannel提供了map方法把文件映射到虛擬內(nèi)存,通常情況可以映射整個(gè)文件,如果文件比較大,可以進(jìn)行分段映射。

FileChannel中的幾個(gè)變量:MapMode mode:內(nèi)存映像文件訪問(wèn)的方式,共三種: MapMode.READ_ONLY:只讀,試圖修改得到的緩沖區(qū)將導(dǎo)致拋出異常。 MapMode.READ_WRITE:讀/寫,對(duì)得到的緩沖區(qū)的更改最終將寫入文件;但該更改對(duì)映射到同一文件的其他程序不一定是可見的。 MapMode.PRIVATE:私用,可讀可寫,但是修改的內(nèi)容不會(huì)寫入文件,只是buffer自身的改變,這種能力稱之為”copy on write”。position:文件映射時(shí)的起始位置。allocationGranularity:Memory allocation size for mapping buffers,通過(guò)native函數(shù)initIDs初始化。

接下去通過(guò)分析源碼,了解一下map過(guò)程的內(nèi)部實(shí)現(xiàn)。

通過(guò)RandomAccessFile獲取FileChannel。

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

上述實(shí)現(xiàn)可以看出,由于synchronized ,只有一個(gè)線程能夠初始化FileChannel。

通過(guò)FileChannel.map方法,把文件映射到虛擬內(nèi)存,并返回邏輯地址address,實(shí)現(xiàn)如下:

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

上述代碼可以看出,最終map通過(guò)native函數(shù)map0完成文件的映射工作。
1. 如果第一次文件映射導(dǎo)致OOM,則手動(dòng)觸發(fā)垃圾回收,休眠100ms后再次嘗試映射,如果失敗,則拋出異常。
2. 通過(guò)newMappedByteBuffer方法初始化MappedByteBuffer實(shí)例,不過(guò)其最終返回的是DirectByteBuffer的實(shí)例,實(shí)現(xiàn)如下:

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

由于FileChannelImpl和DirectByteBuffer不在同一個(gè)包中,所以有權(quán)限訪問(wèn)問(wèn)題,通過(guò)AccessController類獲取DirectByteBuffer的構(gòu)造器進(jìn)行實(shí)例化。

DirectByteBuffer是MappedByteBuffer的一個(gè)子類,其實(shí)現(xiàn)了對(duì)內(nèi)存的直接操作。

get過(guò)程

MappedByteBuffer的get方法最終通過(guò)DirectByteBuffer.get方法實(shí)現(xiàn)的。

JAVA中NIO通過(guò)MappedByteBuffer操作大文件

map0()函數(shù)返回一個(gè)地址address,這樣就無(wú)需調(diào)用read或write方法對(duì)文件進(jìn)行讀寫,通過(guò)address就能夠操作文件。底層采用unsafe.getByte方法,通過(guò)(address + 偏移量)獲取指定內(nèi)存的數(shù)據(jù)。

第一次訪問(wèn)address所指向的內(nèi)存區(qū)域,導(dǎo)致缺頁(yè)中斷,中斷響應(yīng)函數(shù)會(huì)在交換區(qū)中查找相對(duì)應(yīng)的頁(yè)面,如果找不到(也就是該文件從來(lái)沒有被讀入內(nèi)存的情況),則從硬盤上將文件指定頁(yè)讀取到物理內(nèi)存中(非jvm堆內(nèi)存)。

如果在拷貝數(shù)據(jù)時(shí),發(fā)現(xiàn)物理內(nèi)存不夠用,則會(huì)通過(guò)虛擬內(nèi)存機(jī)制(swap)將暫時(shí)不用的物理頁(yè)面交換到硬盤的虛擬內(nèi)存中。

性能分析

從代碼層面上看,從硬盤上將文件讀入內(nèi)存,都要經(jīng)過(guò)文件系統(tǒng)進(jìn)行數(shù)據(jù)拷貝,并且數(shù)據(jù)拷貝操作是由文件系統(tǒng)和硬件驅(qū)動(dòng)實(shí)現(xiàn)的,理論上來(lái)說(shuō),拷貝數(shù)據(jù)的效率是一樣的。
但是通過(guò)內(nèi)存映射的方法訪問(wèn)硬盤上的文件,效率要比read和write系統(tǒng)調(diào)用高,這是為什么?

read()是系統(tǒng)調(diào)用,首先將文件從硬盤拷貝到內(nèi)核空間的一個(gè)緩沖區(qū),再將這些數(shù)據(jù)拷貝到用戶空間,實(shí)際上進(jìn)行了兩次數(shù)據(jù)拷貝;

map()也是系統(tǒng)調(diào)用,但沒有進(jìn)行數(shù)據(jù)拷貝,當(dāng)缺頁(yè)中斷發(fā)生時(shí),直接將文件從硬盤拷貝到用戶空間,只進(jìn)行了一次數(shù)據(jù)拷貝。

所以,采用內(nèi)存映射的讀寫效率要比傳統(tǒng)的read/write性能高。

總結(jié)

MappedByteBuffer使用虛擬內(nèi)存,因此分配(map)的內(nèi)存大小不受JVM的-Xmx參數(shù)限制,但是也是有大小限制的。

如果當(dāng)文件超出1.5G限制時(shí),可以通過(guò)position參數(shù)重新map文件后面的內(nèi)容。

MappedByteBuffer在處理大文件時(shí)的確性能很高,但也存在一些問(wèn)題,如內(nèi)存占用、文件關(guān)閉不確定,被其打開的文件只有在垃圾回收的才會(huì)被關(guān)閉,而且這個(gè)時(shí)間點(diǎn)是不確定的。
javadoc中也提到:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.*

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2903

    瀏覽量

    73536
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2943

    瀏覽量

    104096
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何修改buildroot和debian文件系統(tǒng)

    本文檔主要介紹在沒有編譯環(huán)境的情況下,如何修改buildroot和debian文件系統(tǒng)方法,如在buildroot文件系統(tǒng)添加文件、修改目錄等文件
    的頭像 發(fā)表于 07-22 17:46 ?281次閱讀
    如何修改buildroot和debian<b class='flag-5'>文件</b>系統(tǒng)

    java環(huán)境配置成功后怎么運(yùn)行

    Java環(huán)境配置成功后,我們可以使用幾種方式來(lái)運(yùn)行Java程序。下面將詳細(xì)介紹這幾種方式以及其使用方法。 命令行運(yùn)行方式 在成功配置Java環(huán)境后,我們可以通過(guò)命令行來(lái)運(yùn)行
    的頭像 發(fā)表于 12-06 15:57 ?1294次閱讀

    java環(huán)境配置成功后能執(zhí)行哪些操作

    Java環(huán)境配置成功后,您可以進(jìn)行以下操作: 編寫和運(yùn)行Java程序:配置成功后,您可以使用Java編寫程序,運(yùn)行并測(cè)試它們。您可以使用任何文本編輯器編寫
    的頭像 發(fā)表于 12-06 15:55 ?589次閱讀

    java程序必須以什么為文件擴(kuò)展名

    “一次編寫,到處運(yùn)行”的原則。 Java程序通常以.java文件擴(kuò)展名。在編碼過(guò)程,開發(fā)者需要使用文本編輯器(如Notepad++、Sublime Text等)來(lái)編寫
    的頭像 發(fā)表于 11-29 14:24 ?898次閱讀

    java如何清空obj文件內(nèi)容

    清空一個(gè)文件的內(nèi)容可以通過(guò)以下步驟來(lái)完成。 首先,你需要指定要清空的文件的路徑。你可以使用Java的File類來(lái)操作文件。以下是一個(gè)使用Fi
    的頭像 發(fā)表于 11-21 10:29 ?384次閱讀

    shell調(diào)用java并返回執(zhí)行結(jié)果

    執(zhí)行結(jié)果:在Shell腳本,使用 java 命令執(zhí)行Java程序,并通過(guò)重定向操作符將輸出結(jié)果保存到一個(gè)變量
    的頭像 發(fā)表于 11-08 10:32 ?1195次閱讀

    “雙11”華為云CDN大文件下載加速,更快更穩(wěn)更優(yōu)質(zhì)

    在信息化時(shí)代,從互聯(lián)網(wǎng)獲取各類軟件、電影、游戲內(nèi)容是我們網(wǎng)上沖浪的基本需求,對(duì)大文件下載獲取也是很常見的場(chǎng)景,如對(duì)視頻內(nèi)容下載、手機(jī)應(yīng)用商店下載、游戲安裝包下載或更新、以及手機(jī)操作系統(tǒng)更新等類型
    的頭像 發(fā)表于 11-07 09:20 ?358次閱讀
    “雙11”華為云CDN<b class='flag-5'>大文件</b>下載加速,更快更穩(wěn)更優(yōu)質(zhì)

    如何用Rust通過(guò)JNI和Java進(jìn)行交互

    近期工作中有Rust和Java互相調(diào)用需求,這篇文章主要介紹如何用Rust通過(guò)JNI和Java進(jìn)行交互,還有記錄一下開發(fā)過(guò)程遇到的一些坑。
    的頭像 發(fā)表于 10-17 11:41 ?633次閱讀

    java的壓縮文件是如何解壓與壓縮的

    (String name):表示壓縮文件的一個(gè)文件或者目錄 void putNextEntry(ZipEntry e):寫入新的壓縮文件或者目錄 ZipIutputStream:是一
    的頭像 發(fā)表于 10-10 15:49 ?734次閱讀

    Java序列化怎么使用

    java 對(duì)象經(jīng)常需要在網(wǎng)絡(luò)以 socket 傳輸或者需要保存到文件。這時(shí)不管 java 對(duì)象是文件
    的頭像 發(fā)表于 10-10 14:19 ?351次閱讀

    Java不同的算法

    在本文中,我們將討論使用 Java 驗(yàn)證一個(gè)給定的字符串是否具有操作系統(tǒng)的有效文件名的不同方法。我們可以根據(jù)限制的字符或長(zhǎng)度限制來(lái)檢查該值。 我們將只關(guān)注核心解決方案,不使用任何外部依賴。我們將使
    的頭像 發(fā)表于 10-08 11:43 ?605次閱讀

    Java多線程的用法

    能力。 什么是進(jìn)程 是指正在運(yùn)行的程序的實(shí)例。 每個(gè)進(jìn)程都擁有自己的內(nèi)存空間、代碼、數(shù)據(jù)和文件等資源,可以獨(dú)立運(yùn)行、調(diào)度和管理。在操作系統(tǒng),進(jìn)程是系統(tǒng)資源分配的最小單位,是實(shí)現(xiàn)多任務(wù)的基礎(chǔ)。
    的頭像 發(fā)表于 09-30 17:07 ?843次閱讀

    如何通過(guò)注解來(lái)優(yōu)化我們的Java代碼

    Java注解可以說(shuō)是我們編碼過(guò)程中最常用的。本篇文章將給大家介紹Java注解的概念、作用以及如何使用注解來(lái)提升代碼的可讀性和靈活性,并介紹如何通過(guò)注解來(lái)優(yōu)化我們的Java代碼。 1、什
    的頭像 發(fā)表于 09-30 11:39 ?509次閱讀

    java的IO流與Guava工具

    Guava IO 日常系統(tǒng)交互,文件的上傳下載都是常見的,一般我們會(huì)通過(guò)jdk提供的IO操作庫(kù)幫助我們實(shí)現(xiàn)。IO指的是數(shù)據(jù)相對(duì)當(dāng)前操作程序
    的頭像 發(fā)表于 09-25 16:24 ?629次閱讀

    IO與NIO有何區(qū)別

    NIO 提到IO,這是Java提供的一套類庫(kù),用于支持應(yīng)用程序與內(nèi)存、文件、網(wǎng)絡(luò)間進(jìn)行數(shù)據(jù)交互,實(shí)現(xiàn)數(shù)據(jù)寫入與輸出。JDK自從1.4版本后,提供了另一套類庫(kù)NIO,我們平時(shí)習(xí)慣稱呼為N
    的頭像 發(fā)表于 09-25 11:00 ?704次閱讀
    IO與<b class='flag-5'>NIO</b>有何區(qū)別