欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺出MappedByteBuffer(推薦)

 更新時(shí)間:2022年12月10日 10:59:27   作者:ZNineSun  
MappedByteBuffer使用虛擬內(nèi)存,因此分配(map)的內(nèi)存大小不受JVM的-Xmx參數(shù)限制,但是也是有大小限制的,這篇文章主要介紹了MappedByteBuffer的基本知識(shí),需要的朋友可以參考下

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

在深入MappedByteBuffer之前,先看看計(jì)算機(jī)內(nèi)存管理的一些知識(shí):

1.內(nèi)存管理

  • 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)存。

那么問(wèn)題來(lá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)沒(méi)有對(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)度。

我們簡(jiǎn)單總結(jié)一下上面的內(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ù)了。

2.MappedByteBuffer的深度剖析

從繼承結(jié)構(gòu)上看,MappedByteBuffer繼承自ByteBuffer,內(nèi)部維護(hù)了一個(gè)邏輯地址address。
我們通過(guò)一個(gè)簡(jiǎn)單的示例看看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) {
        }
    }
}

上面代碼是不是看的不是很明白,我逐一給大家解釋一下

整個(gè)代碼其實(shí)就包含了兩個(gè)過(guò)程:

  • map過(guò)程
  • get過(guò)程

2.1 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初始化。、

因?yàn)镸appedByteBuffer做的實(shí)在是太過(guò)于優(yōu)秀了,所以我不得不帶著大家讀一下源碼,看看他們的設(shè)計(jì)思想和思路

1.通過(guò)RandomAccessFile獲取FileChannel

public final FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, path, true, rw, this);
        }
        return channel;
    }
}

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

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

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通過(guò)native函數(shù)map0完成文件的映射工作。

  • 如果第一次文件映射導(dǎo)致OOM,則手動(dòng)觸發(fā)垃圾回收,休眠100ms后再次嘗試映射,如果失敗,則拋出異常。
  • 通過(guò)newMappedByteBuffer方法初始化MappedByteBuffer實(shí)例,不過(guò)其最終返回的是DirectByteBuffer的實(shí)例,實(shí)現(xiàn)如下:
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;
}
// 訪問(wèn)權(quán)限
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不在同一個(gè)包中,所以有權(quán)限訪問(wèn)問(wèn)題,通過(guò)AccessController類獲取DirectByteBuffer的構(gòu)造器進(jìn)行實(shí)例化。

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

2.2 get過(guò)程

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

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()函數(shù)返回一個(gè)地址address,這樣就無(wú)需調(diào)用readwrite方法對(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)沒(mé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)存中。

3.性能分析

從代碼層面上看,從硬盤上將文件讀入內(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)用,但沒(méi)有進(jìn)行數(shù)據(jù)拷貝,當(dāng)缺頁(yè)中斷發(fā)生時(shí),直接將文件從硬盤拷貝到用戶空間,只進(jìn)行了一次數(shù)據(jù)拷貝。

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

4.總結(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.*

翻譯過(guò)來(lái)便是:

映射的字節(jié)緩沖區(qū)及其表示的文件映射在緩沖區(qū)本身被垃圾收集之前保持有效

到此這篇關(guān)于深入淺出MappedByteBuffer的文章就介紹到這了,更多相關(guān)MappedByteBuffer簡(jiǎn)介內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringAop切入點(diǎn)execution表達(dá)式的深入講解

    SpringAop切入點(diǎn)execution表達(dá)式的深入講解

    Spring AOP 可能會(huì)經(jīng)常使用 execution切入點(diǎn)指示符,下面這篇文章主要給大家介紹了關(guān)于SpringAop切入點(diǎn)execution表達(dá)式的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • java實(shí)現(xiàn)對(duì)對(duì)碰小游戲

    java實(shí)現(xiàn)對(duì)對(duì)碰小游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)對(duì)對(duì)碰小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • Java RPC框架如何實(shí)現(xiàn)客戶端限流配置

    Java RPC框架如何實(shí)現(xiàn)客戶端限流配置

    這篇文章主要介紹了Java RPC框架如何實(shí)現(xiàn)客戶端限流配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • 淺析Android系統(tǒng)中HTTPS通信的實(shí)現(xiàn)

    淺析Android系統(tǒng)中HTTPS通信的實(shí)現(xiàn)

    這篇文章主要介紹了淺析Android系統(tǒng)中HTTPS通信的實(shí)現(xiàn),實(shí)現(xiàn)握手的源碼為Java語(yǔ)言編寫,需要的朋友可以參考下
    2015-07-07
  • java8 利用reduce實(shí)現(xiàn)將列表中的多個(gè)元素的屬性求和并返回操作

    java8 利用reduce實(shí)現(xiàn)將列表中的多個(gè)元素的屬性求和并返回操作

    這篇文章主要介紹了java8 利用reduce實(shí)現(xiàn)將列表中的多個(gè)元素的屬性求和并返回操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • SpringMVC體系分層模式原理圖解

    SpringMVC體系分層模式原理圖解

    這篇文章主要介紹了SpringMVC體系分層模式原理圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java實(shí)現(xiàn)寵物商店管理

    Java實(shí)現(xiàn)寵物商店管理

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)寵物商店管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • Spring注入Bean的一些方式總結(jié)

    Spring注入Bean的一些方式總結(jié)

    這篇文章主要給大家總結(jié)介紹了關(guān)于Spring注入Bean的一些方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • springboot項(xiàng)目集成swagger-bootstrap-ui全過(guò)程

    springboot項(xiàng)目集成swagger-bootstrap-ui全過(guò)程

    這篇文章主要介紹了springboot項(xiàng)目集成swagger-bootstrap-ui全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Java 中校驗(yàn)時(shí)間格式的常見方法

    Java 中校驗(yàn)時(shí)間格式的常見方法

    在實(shí)際項(xiàng)目開發(fā)中,跟時(shí)間參數(shù)打交道是必不可少的,為了保證程序的安全性、健壯性,一般都會(huì)對(duì)參數(shù)進(jìn)行校驗(yàn),其他類型的參數(shù)校驗(yàn)很好實(shí)現(xiàn),那你知道時(shí)間參數(shù)的是怎么校驗(yàn)的嗎,下面給大家分享Java 中校驗(yàn)時(shí)間格式的方法,感興趣的朋友跟隨小編一起看看吧
    2024-08-08

最新評(píng)論