0

0

深入浅出 Java FileChannel 的堆外内存使用丨社区分享

爱谁谁

爱谁谁

发布时间:2025-07-17 10:46:22

|

532人浏览过

|

来源于php中文网

原创

在一个风和日丽的下午(标准开头),突然收到用户紧急反馈,线上系统 iotdb 查询卡住。经过一番排查,发现 iotdb 在读取数据文件时使用到了 filechannel,而 filechannel 使用的堆外内存引发了系统 oom。问题定位后,成功帮助用户解决了问题。本文由此引出主题:filechannel 中堆外内存的使用。

首先介绍一些背景知识:

关于 FileChannel

Java NIO 是一种基于通道(Channel)和缓冲区(Buffer)的 I/O 方式,而 FileChannel 是 Java NIO 中用于读写文件的通道。不同于传统文件 I/O 面向文件流顺序读写的方式,FileChannel 是将数据从通道读取到缓冲区中,或者从缓冲区写入到通道中。

由于 FileChannel 能够随机访问读取到缓冲区的数据,因此非常适合将文件中特定位置的数据块加载到内存中。在 Apache IoTDB 中,每次读取数据文件(即 TsFile 文件)往往只读取一个数据块(Chunk 或 Page),使用 FileChannel 是非常合适的。关于 Apache IoTDB 中对 FileChannel 的使用,可以查阅代码:

立即学习Java免费学习笔记(深入)”;

org.apache.iotdb.tsfile.read.reader.LocalTsFileInput

https://www.php.cn/link/f02e21c27440aef2e5c495ce615279e3

关于 堆外内存

堆外内存是直接从操作系统中分配的内存,它不属于 JVM 运行时数据区的一部分,也不是 JVM 规范中定义的内存区域,因此不受 Java 堆大小的限制,但仍然会受到本机总内存的大小及处理器寻址空间的限制,也可能导致 OOM 异常出现。

堆外内存的最大大小可以通过 -XX:MaxDirectMemorySize 设置;如果不指定,默认与堆的最大值 -Xmx 参数值一致。

可以在 Java VisualVM 中安装插件 Buffer Pools 来监控堆外内存。

为什么 FileChannel 要使用堆外内存?

FileChannel 中所有的 I/O 操作需要通过缓冲区进行,例如 ByteBuffer,而 ByteBuffer 有两种:

  1. HeapByteBuffer:堆上的 ByteBuffer 对象,调用 ByteBuffer.allocate() 分配,是在 Java 堆上分配的存储空间,属于 JVM 管理的范围。

  2. DirectByteBuffer:调用 ByteBuffer.allocateDirect() 分配,在堆外内存上分配存储空间,在 Java 堆上有一个堆外内存的引用对象。

如果使用 HeapByteBuffer,数据在 Java 堆上,操作系统处理时需要把堆上的数据拷贝到操作系统里(JVM 运行内存之外)某一块内存空间中,然后再进行 I/O 操作。如果使用 DirectByteBuffer,因为数据本来就在堆外内存中,所以跟 I/O 设备交互的时候没有拷贝的过程,提升了效率,这种特性称为“零拷贝”。

那为什么操作系统一定要将数据拷贝到堆外内存呢?这是由于 write、read 等函数进行系统调用时,参数传的是内存地址,而 JVM 进行 GC 时,会对 Java 堆进行碎片整理,移动对象在内存中的位置,进而导致内存地址的变化。如果在 I/O 操作进行中发生了 GC,内存地址发生变化,I/O 操作的数据就全乱套了。而堆外内存是不受 GC 控制的,因此需要把数据拷贝到堆外内存之后再进行 I/O 操作。

总结

在 NIO 中,由于要求操作数据的内存地址在 I/O 过程中保持不变,因此需要将数据拷贝到堆外内存。FileChannel 使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的直接引用进行操作,从而避免了在 Java 堆和 Native 堆中来回复制数据,在一些场景下能够显著提高性能。

JDK 源码分析

在以上分析的基础上,我们深入 JDK 的源码(JDK 1.8.0_311),进一步加深理解。

DirectByteBuffer 的分配

基本流程:首先向 Bits 类申请内存额度,如果申请成功,调用 Unsafe 类分配内存、初始化内存。同时需要创建 cleaner,用于堆外内存的回收。

深入浅出 Java FileChannel 的堆外内存使用丨社区分享向 Bits 类申请内存额度:

深入浅出 Java FileChannel 的堆外内存使用丨社区分享这里值得注意的一点的是:如果开启 -XX:+DisableExplicitGC,System.gc() 无效,可能导致堆外内存无法有效回收,存在潜在的内存泄露风险。

判断申请额度是否超出限制:

Kacha
Kacha

KaCha是一款革命性的AI写真工具,用AI技术将照片变成杰作!

下载

深入浅出 Java FileChannel 的堆外内存使用丨社区分享DirectByteBuffer 的回收

DirectByteBuffer 在分配时创建的 Cleaner 继承自虚引用(PhantomReference),当 DirectByteBuffer 仅被 Cleaner 引用(即为虚引用)时,其可以在任意 GC 时段被回收。

虚引用与引用队列(ReferenceQueue)结合使用,可以实现虚引用关联对象被垃圾回收时,进行系统通知、资源清理等功能。如下图所示,当某个被 Cleaner 引用的对象将被回收时,JVM 垃圾收集器会将此对象的引用放入到对象引用中的 pending 链表中,等待 Reference-Handler 进行相关处理。其中,Reference-Handler 为一个拥有最高优先级的守护线程,会循环不断的处理 pending 链表中的对象引用,执行 Cleaner 的 clean 方法进行相关清理工作。

深入浅出 Java FileChannel 的堆外内存使用丨社区分享(来源:

https://www.php.cn/link/53c6684db6a8f413504063163d94972b

当 DirectByteBuffer 实例对象被回收时,在 Reference-Handler 线程操作中,会调用 Cleaner 的 clean 方法根据创建 Cleaner 时传入的 Deallocator 来进行堆外内存的释放(即调用 Unsafe 类释放内存,如下图所示)。

深入浅出 Java FileChannel 的堆外内存使用丨社区分享从以上源码分析中可知,堆外内存并非完全不受 GC 控制。

这里还有一种有趣的描述:虽然 DirectByteBuffer 存在于 Java 堆内的对象很小,但可能对应了一大段堆外内存,这种对象被称为“冰山对象”。这种“冰山对象”可能会引发一些问题,由于 DirectByteBuffer 对象很小,不容易被 GC 回收,占用的大块堆外内存也就不容易释放。

深入浅出 Java FileChannel 的堆外内存使用丨社区分享FileChannel 读写中 DirectByteBuffer 的分配与回收

FileChannel 使用 IOUtil 来进行读写,这里仅分析读流程,写流程与之类似。

深入浅出 Java FileChannel 的堆外内存使用丨社区分享申请临时 DirectByteBuffer:

深入浅出 Java FileChannel 的堆外内存使用丨社区分享其中,有两点值得注意:

  1. bufferCache 是一个 ThreadLocal 对象。

  2. MAX_CACHED_BUFFER_SIZE 可以通过 -Djdk.nio.maxCachedBufferSize 配置,否则会设置为 Long.MAX_VALUE。

释放或缓存临时 DirectByteBuffer:

深入浅出 Java FileChannel 的堆外内存使用丨社区分享深入浅出 Java FileChannel 的堆外内存使用丨社区分享这里的 TEMP_BUF_POOL_SIZE 被设置为 IOUtil.IOV_MAX ,该值与操作系统有关,在 Linux 系统上默认值是 1024,Win10 系统该值为 16。

线上系统 OOM 的原因及解决

回到开头说的线上问题,细心的同学可能已经从上面的源码分析中发现了问题。

如果没有配置 MAX_CACHED_BUFFER_SIZE,由于其默认值非常大,所以几乎不会有直接分配的情况,而是使用 bufferCache 这个 ThreadLocal 变量来进行缓存,从而复用。这意味着,线程越多,这块临时的堆外内存缓存就越大。而在该用户场景下足足开了 80 个查询线程,用户的数据文件中数据块又比较大,所以额外分配这块堆外内存缓存导致了 OOM。

定位了问题,解决方案就呼之欲出了:

[IOTDB-2195] 对查询线程数进行限制。

[IOTDB-2061] 限制 FileChannel 每次读取的数据量:对于较大的数据块,分批读取,在内存中拼接返回。

[IOTDB-2076] 限制合并生成的数据块(Chunk)大小。

[IOTDB-2061] 通过 -Djdk.nio.maxCachedBufferSize 限制缓存的 buffer 大小。

作为全球性开源项目,截至目前,Apache IoTDB已拥有165+ 名贡献者、1.8KStar、560+ Fork。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

870

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

30

2025.12.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

261

2025.11.14

golang channel相关教程
golang channel相关教程

本专题整合了golang处理channel相关教程,阅读专题下面的文章了解更多详细内容。

351

2025.11.17

github中文官网入口 github中文版官网网页进入
github中文官网入口 github中文版官网网页进入

github中文官网入口https://docs.github.com/zh/get-started,GitHub 是一种基于云的平台,可在其中存储、共享并与他人一起编写代码。 通过将代码存储在GitHub 上的“存储库”中,你可以: “展示或共享”你的工作。 持续“跟踪和管理”对代码的更改。

4126

2026.01.21

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.1万人学习

Java 教程
Java 教程

共578课时 | 80.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号