Java NIO Buffer概念

针对每一种基本类型的 Buffer ,NIO 又根据 Buffer 背后的数据存储内存不同分为了:HeapBuffer,DirectBuffer,MappedBuffer。

HeapBuffer 顾名思义它背后的存储内存是在 JVM 堆中分配,在堆中分配一个数组用来存放 Buffer 中的数据。

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{
    // Cached array base offset
    private static final long ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    // 在堆中使用一个数组存放Buffer数据
    final byte[] hb;                  // Non-null only for heap buffers
}

DirectBuffer 背后的存储内存是在堆外内存中分配,MappedBuffer 是通过内存文件映射将文件中的内容直接映射到堆外内存中,其本质也是一个 DirectBuffer 。

由于 DirectBuffer 和 MappedBuffer 背后的存储内存是在堆外内存中分配,不受 JVM 管理,所以不能用一个 Java 基本类型的数组表示,而是直接记录这段堆外内存的起始地址。

public abstract class Buffer {
    ...

    // Used by heap byte buffers or direct buffers with Unsafe access
    // For heap byte buffers this field will be the address relative to the
    // array base address and offset into that array. The address might
    // not align on a word boundary for slices, nor align at a long word
    // (8 byte) boundary for byte[] allocations on 32-bit systems.
    // For direct buffers it is the start address of the memory region. The
    // address might not align on a word boundary for slices, nor when created
    // using JNI, see NewDirectByteBuffer(void*, long).
    // Should ideally be declared final
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    // //堆外内存地址
    long address;
}

用户空间缓冲区:

以给文件写数据为例:

FileChannel fileChannel = new RandomAccessFile(new File("file-read-write.txt"), "rw").getChannel();
ByteBuffer  heapByteBuffer = ByteBuffer.allocate(4096);
fileChannel.write(heapByteBuffer);

sun.nio.ch.FileChannelImpl#write(java.nio.ByteBuffer)

在 IOUtil 中首先创建一个临时的 DirectByteBuffer,然后将 HeapByteBuffer 中的数据全部拷贝到这个临时的 DirectByteBuffer 中。这个 DirectByteBuffer 就是我们在 IO 系统调用中经常提到的用户空间缓冲区。

随后在 writeFromNativeBuffer 方法中通过  FileDispatcher 触发 JNI 层的 native 方法执行底层系统调用 write 。

而 JDK Buffer 也会根据其背后所依赖的虚拟内存在进程虚拟内存空间中具体所属的虚拟内存区域而演变出 HeapByteBuffer , MappedByteBuffer , DirectByteBuffer 。这三种不同类型 ByteBuffer 的本质区别就是其背后依赖的虚拟内存在 JVM 进程虚拟内存空间中的布局位置不同。

JVM 在操作系统的视角来看其实就是一个普通的进程,内核会根据进程在运行期间所需数据的功能特性不同,而为每一类数据专门开辟出一段虚拟内存区域出来。

位于 JVM 堆之外的内存其实都可以归属到 DirectByteBuffer 的范畴中。比如,位于 OS 堆之内,JVM 堆之外的 MetaSpace,即时编译(JIT) 之后的 codecache,JVM 线程栈,Native 线程栈,JNI 相关的内存,等等。 

JVM  在 OS 堆中划分出的 Direct Memory (上图红色部分)特指受到参数 -XX:MaxDirectMemorySize 限制的直接内存区域,比如通过 ByteBuffer#allocateDirect 申请到的 Direct Memory 容量就会受到该参数的限制。

而通过 Unsafe#allocateMemory 申请到的 Direct Memory 容量则不会受任何 JVM 参数的限制,只会受操作系统本身对进程所使用内存容量的限制。也就是说 Unsafe 类会脱离 JVM 直接向操作系统进行内存申请。

HeapByteBuffer 底层依赖的字节数组背后的内存位于 JVM 堆中:

MappedByteBuffer 背后所占用的内存位于 JVM 进程虚拟内存空间中的文件映射与匿名映射区中,系统调用 mmap 映射出来的内存就是在这个区域中划分的。

JDK 仅仅只是对 mmap 文件映射方式进行了封装,所以 MappedByteBuffer 的本质其实是对文件映射与匿名映射区中某一段虚拟映射区域在 JVM 层面上的描述。这段虚拟映射区的起始内存地址 addr 以及映射长度 length 被封装在 MappedByteBuffer 中的 address , capacity 属性中:

mmap:

mmap 有两种映射方式,一种是匿名映射,常用于进程动态的向 OS 申请内存,比如,glibc 库里提供的用于动态申请内存的 malloc 函数,当申请的内存大于 128K 的时候,malloc 就会调用 mmap 采用匿名映射的方式来申请。

另一种就是文件映射,用于将磁盘文件中的某段区域与进程虚拟内存空间中文件映射与匿名映射区里的某段虚拟内存区域进行关联映射。后续我们针对这段映射内存的读写就相当于是对磁盘文件的读写了,整个读写过程没有数据的拷贝,也没有切态的发生(这里特指在完成缺页处理之后)。

当我们调用 mmap 之后,OS 内核只是会为我们分配一段虚拟内存,然后将虚拟内存与磁盘文件进行映射,整个过程都只是在和虚拟内存打交道,并未出现任何物理内存的身影。而这段虚拟内存在 Java 层面就是 MappedByteBuffer。

由于现在我们只是刚刚完成了文件映射,仅仅只是在 JVM 层面得到了一个 MappedByteBuffer,这个 MappedByteBuffer 背后所依赖的虚拟内存就是我们通过 mmap 映射出来的。

此时我们还未对文件进行读写操作,所以该映射文件对应的 page cache 里还是空,没有任何文件页(用于存储文件数据的物理内存页)。而虚拟内存(MappedByteBuffer)与物理内存之间的关联是通过进程页表来完成的,由于此时内核还未对 MappedByteBuffer 分配物理内存,所以 MappedByteBuffer 在 JVM 进程页表中对应的页表项 PTE 还是空的。

当我们开始访问这段 MappedByteBuffer 的时候, CPU 会将 MappedByteBuffer 背后的虚拟内存地址送到 MMU 地址翻译单元中进行地址翻译查找其背后的物理内存地址。

如果 MMU 发现 MappedByteBuffer 在 JVM 进程页表中对应的页表项 PTE 还是空的,这说明 MappedByteBuffer 是刚刚被 mmap 系统调用映射出来的,还没有分配物理内存。

于是 MMU 就会产生缺页中断,随后 JVM  进程切入到内核态,进行缺页处理,为 MappedByteBuffer 分配物理内存。

内核在 do_fault 函数中处理 MappedByteBuffer 缺页的时候,首先会调用 find_get_page 从映射文件的 page cache 中尝试获取文件页,前面已经说了,当 MappedByteBuffer 刚刚被映射出来的时候,映射文件的 page cache 还是空的,没有缓存任何文件页,需要映射到内存的文件内容此时还静静地躺在磁盘上。

当文件页不在 page cache 中,内核则会调用 do_sync_mmap_readahead 来同步预读,这里首先会分配一个物理内存页出来,然后将新分配的内存页加入到 page cache 中,并增加页引用计数。

如果文件页已经缓存在 page cache 中了,则调用 do_async_mmap_readahead 启动异步预读机制,将相邻的若干文件页一起预读进 page cache 中。

随后会通过 address_space_operations (page cache 相关的操作函数集合)中定义的 readpage 激活块设备驱动从磁盘中读取映射的文件内容并填充到 page cache 里的文件页中。

经过 filemap_fault 函数的处理,此时 MappedByteBuffer 背后所映射的文件内容已经加载到 page cache 中了。

后续 JVM 进程在访问这段 MappedByteBuffer 的时候就相当于是直接访问映射文件的 page cache。整个过程是在用户态进行,不需要切态。

不需要切态说明:

后面 JVM  进程对 MappedByteBuffer 的读写就相当于是直接读写 page cache 了,关于这一点,很多读者朋友会有这样的疑问:page cache 是内核态的部分,为什么我们通过用户态的 MappedByteBuffer 就可以直接访问内核态的东西了?

这里大家不要被内核态这三个字给唬住了,虽然 page cache 是属于内核部分的,但其本质上还是一块普通的物理内存,想想我们是怎么访问内存的 ? 不就是先有一段虚拟内存,然后在申请一段物理内存,最后通过进程页表将虚拟内存和物理内存映射起来么,进程在访问虚拟内存的时候,通过页表找到其映射的物理内存地址,然后直接通过物理内存地址访问物理内存。

回到我们讨论的内容中,这段虚拟内存不就是 MappedByteBuffer 吗,物理内存就是 page cache 啊,在通过页表映射起来之后,进程在通过 MappedByteBuffer 访问 page cache 的过程就和访问普通内存的过程是一模一样的。

也正因为 MappedByteBuffer 背后映射的物理内存是内核空间的 page cache,所以它不会消耗任何用户空间的物理内存(JVM  的堆外内存),因此也不会受到 -XX:MaxDirectMemorySize 参数的限制

参考:从 Linux 内核角度探秘 JDK NIO 文件读写本质

从 Linux 内核角度探秘 JDK MappedByteBuffer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/751976.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot基于web模式的师资管理系统的设计与实现-计算机毕业设计源码040928

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设师资管理系统 。 本设…

全球视角下的AI安全挑战:面向未来的准备

云安全联盟大中华区即将推出人工智能安全认证专家&#xff08;Certified Artificial Intelligence Security Professional&#xff0c;CAISP&#xff09;培训及认证计划&#xff0c;将在Q3全面上线。 在全球科技创新的洪流中&#xff0c;人工智能&#xff08;AI&#xff09;无…

vue3.0(十五)状态管理vuex和pinia

文章目录 状态管理vuex1. 什么情况下应该使用 Vuex2. vuex的安装3. vuex的五大属性4. vuex的五大属性在组件中的应用5. 数据持久化 pinia1.Pinia 对比 Vuex2.安装及引入3.三大属性4.三大属性在组件中的运用总结 状态管理 状态管理是指对Vue.js应用中的数据进行统一管理的一种机…

【.Net】Web项目部署腾讯云

文章目录 总述前置准备docker-compose部署普通部署 参考 总述 前置准备 云服务添加端口 另有linux本身防火墙请参考&#xff1a; 【Linux】防火墙命令 需安装.Net SDK和Asp .Net Runtime 注意&#xff1a; 1、sdk也要不只是runtime 2、是Asp .Net Runtime不是.Net Runtime …

擎耀解码汽车大灯照明系统电动调节步进电机位置反馈的解决方案

在现代汽车设计中&#xff0c;智能照明系统扮演着至关重要的角色。其中&#xff0c;汽车大灯的电动调节功能不仅提高了夜间行车的安全性&#xff0c;还增强了车辆的科技感和便利性。然而&#xff0c;要实现精准的大灯角度调节&#xff0c;步进电机的位置反馈机制尤为关键。擎耀…

SpringBoot脚手架MySpringBootAPI(PgSQL+Druid+MyBatisPlus+Lombok)

MySpringBootAPI SpringBoot脚手架&#xff0c;基于SpringBootDruidPgSQLMyBatisPlusFastJSONLombok&#xff0c;其他的请自行添加和配置。 Author powered by Moshow郑锴(大狼狗) , https://zhengkai.blog.csdn.net 如何运行 1.首先确保你是JDK17&#xff0c;推荐微软的MSJDK…

《Windows API每日一练》6.4 程序测试

前面我们讨论了鼠标的一些基础知识&#xff0c;本节我们将通过一些实例来讲解鼠标消息的不同处理方式。 本节必须掌握的知识点&#xff1a; 第36练&#xff1a;鼠标击中测试1 第37练&#xff1a;鼠标击中测试2—增加键盘接口 第38练&#xff1a;鼠标击中测试3—子窗口 第39练&…

ECharts 源码代码规范

代码规范 - Apache EChartsApache ECharts&#xff0c;一款基于JavaScript的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表。https://echarts.apache.org/zh/coding-standard.html 源文件 [强制] JavaScr…

基于ARM的通用的Qt移植思路

文章目录 实验环境介绍一、确认Qt版本二、确认交叉编译工具链三、配置Qt3.1、修改qmake.conf3.2、创建autoConfig.sh配置文件 四、编译安装Qt五、移植Qt安装目录六、配置Qt creator6.1、配置qmake6.2、配置GCC编译器6.3、配置G编译器6.4、配置编译器套件6.5、创建应用 七、总结…

论文速览 | IEEE Signal Processing Letters, 2024 | 基于时空上下文学习的事件相机立体深度估计

论文速览 | IEEE Signal Processing Letters, 2024 | 基于时空上下文学习的事件相机立体深度估计 1 引言 在计算机视觉领域,立体深度估计一直是一个备受关注的研究热点。传统的基于帧的方法虽然取得了长足的进步,但在处理运动模糊、低照度和平坦区域等挑战性场景时仍面临诸多…

二进制方式部署k8s集群

前置知识点 1、生产环境部署K8s集群的两种方式 • kubeadm Kubeadm是一个K8s部署工具&#xff0c;提供kubeadm init和kubeadm join&#xff0c;用于快速部署Kubernetes集群。 • 二进制包 从github下载发行版的二进制包&#xff0c;手动部署每个组件&#xff0c;组成Kub…

Linux的fread函数

fread函数 从文件中读入数据到指定的地址中 函数原型 : size_t fread(void*buff , size_t size, size_t count , FILE* stream) /* * description : 对已打开的流进行数据读取 * param ‐ ptr &#xff1a;指向 数据块的指针 * param ‐ size &#xff1a;指定读取的每…

LabVIEW编程控制ABB机械臂

使用LabVIEW编程控制ABB机械臂是一项复杂但十分有价值的任务。通过LabVIEW&#xff0c;可以实现对机械臂的精确控制和监控&#xff0c;提升自动化水平和操作效率。 1. 项目规划和硬件选型 1.1 确定系统需求 运动控制&#xff1a;确定机械臂需要执行的任务&#xff0c;如抓取、…

【总线】AXI4第四课时:握手机制详解

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

乐观锁和悲观锁(MySQL和Java)

乐观锁和悲观锁(MySQL和Java) 在并发编程中&#xff0c;为了确保数据的一致性和完整性&#xff0c;我们通常需要使用锁机制来控制对共享资源的访问。锁主要分为两种&#xff1a;乐观锁和悲观锁。本文将详细介绍这两种锁的概念、工作原理以及它们的优缺点。 悲观锁 悲观锁(Pe…

LabVIEW电涡流检测系统

开发了一种基于LabVIEW的软件与硬件结合的电涡流检测系统&#xff0c;通过同步采样技术和编码器的协同工作&#xff0c;显著提高了大型结构物的损伤检测精度和效率&#xff0c;具有良好的应用前景和实用价值。 项目背景 传统的手持式电涡流检测方法因其速度慢、灵敏度低、准确…

根文件系统

根文件系统 1 介绍1.1 根文件系统介绍1.2 根文件系统目录1.3 常见的根文件系统 2 Buildroot 根文件系统的构建2.1 介绍2.2 依赖文件2.3 交叉编译工具2.4 构建2.4.1 配置 Target options2.4.2 配置 Toolchain2.4.3 配置 System configuration2.4.4 配置 Filesystem images2.4.5 …

微服务知识

传统架构 传统架构会出现的问题 配置烦琐&#xff0c;上线容易出错 加机器要重启 负载均衡单点 管理困难 CAP原则。 CAP原则是指在一个分布式系统中&#xff0c;Consistency&#xff08;一致性&#xff09;、Availability&#xff08;可用性&#xff09;、Partition Toleranc…

产品中心|高效能双处理器Xilinx FPGA 4通道射频收发板卡

1、产品概述 基于Xilinx XC7K325T芯片的4通道射频收发板卡&#xff0c;搭载高能效Cortex-A8内核处理器、1组16bit/2GB DDR3及1组4GB DDR3、 1组2GB Nand Flash、1路USB接口、4路高速ADC、4路高速DAC&#xff0c;支持外触发&#xff0c;外时钟。用于FPGA程序加载板卡工作温度范…

Zynq7000系列FPGA中的DMA控制器简介(一)

DMA控制器&#xff08;DMAC&#xff09;使用64位AXI主接口来执行与系统存储器和PL外围设备之间的DMA数据传输&#xff0c;操作频率同CPU_2x的时钟速率。传输由DMA指令执行引擎控制。DMA引擎运行在一个小指令集上&#xff0c;该指令集提供了一种灵活的指定DMA传输的方法。这种方…