java中使用mmap技术的实现示例

 更新时间:2026年01月28日 09:14:37   作者:Java编程爱好者  
本文主要介绍了java中使用mmap技术的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

jdk21 之后,随着 FFM 加入并稳定,现在 java 中也可以直接使用 mmap 技术将文件直接映射进内存并读取了,并且没有 nio 中 21 亿的限制(Integer.MAX_VALUE)。

BIO时代

try (FileInputStream fis = new FileInputStream("file.txt")) {
    byte[] buffer = new byte[8192]; // 8KB 缓冲区
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        // 处理 buffer 中的前 bytesRead 字节
    }
}

最早读取文件就是阻塞读取,由于 byte[] 是jdk管理,自己传入的,所以下标最大就是21亿,而且由于正常情况下由代码传入,所以不可能 new byte[Integer.MAX_VALUE],而且 byte[] 回收需要看 gc。

NIO时代(JDK 1.4+)

try (FileChannel channel = FileChannel.open(Paths.get("data.txt"), StandardOpenOption.READ)) {
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
}

后来 java 推出了 NIO,但是作者本人实际工作接触的需要如此优化的场景太少,且 api 并不直观,导致完全没有用过几次。返回的 MappedByteBuffer api 中下标参数使用的是 int,这直接导致超过 4G 的文件必须分段映射,且 MappedByteBuffer 映射的内存也需要 gc 回收。

属于初代 mmap 技术,虽然原理上是 mmap,但是限制颇多。

AIO时代(JDK 7+)

本质上是 NIO.2,或者叫 new NIO,原则上属于NIO的一部分

AsynchronousFileChannel

这个 api 更是查无此人,作者并不知道这个应该怎么用/有什么性能。

FFM(JDK 22+)

try (var arena = Arena.ofConfined();
     var channel = FileChannel.open(path, StandardOpenOption.READ)) {
    var size = channel.size();
    var memorySegment = channel.map(FileChannel.MapMode.READ_ONLY,
            0,
            size,
            arena
    );

} catch (IOException e) {
    throw new RuntimeException(e);
}

随着 FFM 稳定,原本的 FileChannel api 中也加入了对 FFM 的支持,使用 Arena 配置接下来申请的内存的生命周期管理,再将 arena 传入 channel,返回 MemorySegment,使用 MemorySegment 即可对全文件进行随机读写。arena 关闭后申请的所有内存直接回收,没有 gc 压力。

由于调用的是底层的 mmap 返回的内存段,所以直接读写 memorySegment 即可直接反应到文件上,也可以调用 MemorySegment#force() 强制写入。

更加接近底层的 mmap 技术,可选择是否有 gc 压力。

读取示例:

public class MemorySegmentFileTest {
    private static final Path path = Path.of("/home/bin-/Downloads/home/extraData.img");

    static void main() {
        try (var arena = Arena.ofConfined();
             var channel = FileChannel.open(path, StandardOpenOption.READ)) {
            var size = channel.size();
            var memorySegment = channel.map(FileChannel.MapMode.READ_ONLY,
                    0,
                    size,
                    arena
            );

            System.out.println("成功映射文件,大小:" + printSize(size));
            System.out.println("内存段地址:" + memorySegment.address());
            System.out.println("内存段大小:" + printSize(memorySegment.byteSize()));
            System.out.println("内存段内容前16字节:");
            for (long i = 0; i < 16; i++) {
                byte b = memorySegment.get(ValueLayout.JAVA_BYTE, i);
                System.out.printf("0x%02X ", b);
            }
            System.out.println();
            System.out.println("内存段内容后16字节:");
            for (long i = size - 16; i < size; i++) {
                byte b = memorySegment.get(ValueLayout.JAVA_BYTE, i);
                System.out.printf("0x%02X ", b);
            }
            System.out.println();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static String printSize(long size) {
        String[] units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
        long s = size;
        var list = new ArrayList<String>();
        var builder = new StringBuilder();
        var formatter = new Formatter(builder);
        for (var unit : units) {
            int sub = (int) (s & ((1 << 10) - 1));
            formatter.format("%s %s", sub, unit);
            list.add(builder.toString());
            builder.setLength(0);
            if (s < 1024) {
                break;
            }
            s >>= 10;
        }
        return String.join(" ", list.reversed());
    }
}

原理说明

mmap 只是建立了虚拟内存地址到磁盘文件的映射,并没有真正加载数据(Lazy Loading)。只有真正读取数据时,OS 才会发生缺页中断(Page Fault)去加载数据。

类似应用

  • 数据库系统:SQLite、MySQL、Redis
  • Kafka
  • RocketMQ

已知缺点

  • 本质是在借用 OS 的 Page Cache,如果同时运行数据库(MySQL/Redis)等应用,java 使用 mmap 疯狂读取大文件可能会把数据库在 Page Cache 里的热数据挤出去(Eviction)。

结尾

本文只是提出一种新版本 jdk 的全新 mmap 使用方式,与其他相对较老的方式相比,这种方式可以直接操纵堆外内存,且性能更高,还支持随机读取,唯一缺点可能就是 api 太新,用起来可能需要重新研究写法。

到此这篇关于java中使用mmap技术的实现示例的文章就介绍到这了,更多相关java使用mmap技术内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Jenkins配置jdk、maven、git方式

    Jenkins配置jdk、maven、git方式

    为了在Jenkins中使用Java、Maven和Git,需要在系统管理的全局工具配置中设置这些工具,首先,确保Jenkins主机已经安装了JDK、Git、Maven以及Maven的settings文件,安装完成后,按照提示配置各个工具
    2023-04-04
  • Java排列组合字符串的方法

    Java排列组合字符串的方法

    这篇文章主要介绍了Java排列组合字符串的方法
    2018-02-02
  • 从JVM分析Java的类的加载和卸载机制

    从JVM分析Java的类的加载和卸载机制

    这篇文章主要介绍了从JVM分析Java的类的加载和卸载机制,讲解了Java类的声明周期,需要的朋友可以参考下
    2015-11-11
  • SpringBoot配置文件注入值的简单实现

    SpringBoot配置文件注入值的简单实现

    本文主要介绍了SpringBoot配置文件注入值的简单实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-11-11
  • Java泛型实现类型安全的通用类型转换器

    Java泛型实现类型安全的通用类型转换器

    在开发中,我们常常需要在不同类型之间进行转换,为了提高代码的可读性与安全性,Java的泛型机制提供了强大的类型检查能力,下面我们就来看看如何通过泛型实现类型安全的通用转换器
    2024-11-11
  • springboot+thymeleaf+mybatis实现甘特图的详细过程

    springboot+thymeleaf+mybatis实现甘特图的详细过程

    这篇文章主要介绍了springboot+thymeleaf+mybatis实现甘特图的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • 2022版IDEA创建一个maven项目的超详细图文教程

    2022版IDEA创建一个maven项目的超详细图文教程

    IDEA是用于java语言开发的集成环境,并且经常用于maven、spring、MyBatis等项目的开发,下面这篇文章主要给大家介绍了关于2022版IDEA创建一个maven项目的超详细图文教程,需要的朋友可以参考下
    2023-02-02
  • 关于Maven中pom.xml文件配置详解

    关于Maven中pom.xml文件配置详解

    pom.xml是Maven项目的核心配置文件,它描述了项目的结构、依赖关系、构建配置等信息,通过合理配置pom.xml,可以提高项目的可维护性和构建效率
    2025-01-01
  • Java语言中&&与& ||与|的区别是什么

    Java语言中&&与& ||与|的区别是什么

    这篇文章主要介绍了Java语言中&&与& ||与|的区别是什么的相关资料,需要的朋友可以参考下
    2017-04-04
  • spring多数据源配置实现方法实例分析

    spring多数据源配置实现方法实例分析

    这篇文章主要介绍了spring多数据源配置实现方法,结合实例形式分析了spring多数据源配置相关操作技巧与使用注意事项,需要的朋友可以参考下
    2019-12-12

最新评论