MappedByteBuffer 详解(图解+秒懂+史上最全)

本文详细介绍了MappedByteBuffer在JavaNIO中的作用,以及如何通过内存映射技术实现高效的大文件操作。同时,文章指出高可用、高并发中间件如RocketMQ的原理和实操对于开发者的重要性,特别是对于提升到高级开发和架构师角色的关键作用。文中还探讨了内存管理的基本概念,包括虚拟内存和物理内存的区别,以及如何通过内存映射实现文件和内存之间的交互。

背景:

在尼恩视频版本里,从架构师视角,尼恩为大家彻底介绍 rocketmq 高可用、高并发中间件的原理与实操。

给大家底层的解读清楚 rocketmq 架构设计、源码设计、工业级高可用实操,含好多复杂度非常高、又非常核心的概念,比如 零复制、延迟容错、工业级RPC框架 ,以横扫全网和史无前例的方式,帮助大家彻底掌握、深入骨髓的掌握 rocketmq, 成为明年3月份征服面试官的神器

在这里插入图片描述

问题:why 高可用、高并发中间件的原理、源码与实操:

实际的开发过程中,很多小伙伴聚焦crud开发,环境出了问题,都不能启动。

作为开发人员,未来的高级开发、架构师,或者未来想走向高端开发,必须掌握高可用、高并发中间件的原理,掌握其实操。

MappedByteBuffer(图解+秒懂+史上最全)

这里 作为 rocketmq 高可用、高并发中间件的原理、源码与实操的前置知识,以博文的方式: 给大家介绍一下 MappedByteBuffer

java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,本文会介绍其性能如此高的内部实现原理。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:码云

内存管理

在深入MappedByteBuffer之前,先看看计算机内存管理的几个术语:

  • MMU:CPU的内存管理单元。
  • 物理内存:即内存条的内存空间。
  • 虚拟内存:计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
  • 交换空间:操作系统反映构建并使用虚拟内存的硬盘空间大小而创建的文件,在windows下,即pagefile.sys文件,其存在意味着物理内存被占满后,将暂时不用的数据移动到硬盘上。
  • 缺页中断:当程序试图访问已映射在虚拟地址空间中但未被加载至物理内存的一个分页时,由MMC发出的中断。如果操作系统判断此次访问是有效的,则尝试将相关的页从虚拟内存文件中载入物理内存。
为什么会有虚拟内存和物理内存的区别?

如果正在运行的一个进程,它所需的内存是有可能大于内存条容量之和的,如内存条是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组成一个物理上真正存在的地址,最后就是访问物理内存的数据了。

Java中基础MMap的使用

MappedByteBuffer是什么?从继承结构上看,MappedByteBuffer继承自ByteBuffer,内部维护了一个逻辑地址address。

将共享内存和磁盘文件建立联系的是文件通道类:FileChannel。

该类的加入是JDK为了统一对外部设备(文件、网络接口等)的访问方法,并且加强了多线程对同一文件进行存取的安全性。

这里只是用它来建立共享内存用,它建立了共享内存和磁盘文件之间的一个通道。

FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。

大致的步骤:

  • 首先通过 RandomAccessFile获取文件通道。
  • 然后,通过channel进行内存映射,获取一个虚拟内存区域VMA
//通过RandomAccessFile获取FileChannel。
try (FileChannel channel = new RandomAccessFile(decodePath, "rw").getChannel();) {
   
   

    //通过channel进行内存映射,获取一个虚拟内存区域VMA
    MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
    ....

channel.map方法的参数:

  • 映射类型

MapMode mode:内存映像文件访问的方式,FileChannel中的几个常量定义,共三种:

  1. MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
  2. MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
  3. MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
  • position:文件映射时的起始位置。
  • length:映射区的长度。长度单位为字节。长度单位为字节

示例1:通过MappedByteBuffer读取文件

package com.crazymakercircle.iodemo.fileDemos;

import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.IOUtil;
import com.crazymakercircle.util.Logger;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created by 尼恩@ 疯创客圈
 */
public class FileMmapDemo {
   
   

    /**
     * 演示程序的入口函数
     *
     * @param args
     */
    public static void main(String[] args) {
   
   
        doMmapDemo();
    }

    /**
     * 读取
     */
    public static void doMmapDemo() {
   
   
        String sourcePath = NioDemoConfig.MMAP_FILE_RESOURCE_SRC_PATH;
        String decodePath = IOUtil.getResourcePath(sourcePath);

        Logger.debug("decodePath=" + decodePath);
        mmapWriteFile(decodePath);
    }


    /**
     * 读取文件内容并输出
     *
     * @param fileName 文件名
     */
    public static void mmapWriteFile(String fileName) {
   
   

        //向文件中存1M的数据
        int length = 1024;//
        try (FileChannel channel = new RandomAccessFile(fileName, "rw").getChannel();) {
   
   

            //一个整数4个字节
            MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
            for (int i = 0; i < length; i++) {
   
   
                mapBuffer.put((byte) (Integer.valueOf('a') + i % 26));
            }
            for (int i = 0; i < length; i++) {
   
   
                if (i % 50 == 0) System.out.println("");
                //像数组一样访问
                System.out.print((char) mapBuffer.get(i));
            }

            mapBuffer.force();

        } catch (
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值