news 2026/1/17 7:16:03

直接内存的释放原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
直接内存的释放原理

直接内存的释放核心依赖 Cleaner 机制(虚引用) ,配合 JVM 垃圾回收触发,无需手动调用 Native 方法,具体原理如下:
一、释放的核心依赖:Cleaner 与虚引用
1. Cleaner 的本质:sun.misc.Cleaner是 Java 中的虚引用实现,继承自PhantomReference,专门用于跟踪对象生命周期并触发资源释放。
2. 关联逻辑:创建DirectByteBuffer时,JVM 会同时创建一个Cleaner对象,将其与DirectByteBuffer绑定。
◦ Cleaner会持有两个关键引用:一是指向DirectByteBuffer的虚引用,二是指向释放直接内存的回调函数(Deallocator)。
◦ DirectByteBuffer本身存储在 Java 堆中,仅作为直接内存的 “引用句柄”,不存储实际数据。
二、释放的完整流程
1. 触发条件:当DirectByteBuffer对象失去所有强引用(如引用被置为null),且 JVM 执行 GC 时,该对象会被标记为可回收。
2. 虚引用触发:GC 发现DirectByteBuffer可回收后,会将关联的Cleaner对象加入到 “引用队列”(ReferenceQueue)。
3. 执行释放回调:JVM 内部有一个后台线程(ReferenceHandler),会持续扫描引用队列。
◦ 当检测到队列中的Cleaner时,会调用其clean()方法。
◦ clean()方法会触发Deallocator的run()方法,通过 Native 调用(如Unsafe.freeMemory())释放对应的直接内存。
4. 最终回收:DirectByteBuffer对象被 GC 从堆中回收,直接内存被操作系统回收,完成整个释放流程。
三、关键细节说明
1. 无需手动调用释放方法:只要DirectByteBuffer的强引用被释放,GC 触发后会自动通过Cleaner完成直接内存释放,无需手动调用freeMemory()。
2. 释放的不确定性:释放时机依赖 GC 触发时机,若DirectByteBuffer长期被强引用(如静态变量持有),GC 不会回收该对象,Cleaner也不会触发,会导致直接内存泄漏。
3. 显式辅助释放:对于大容量直接内存,可主动置空DirectByteBuffer引用(buffer = null),配合System.gc()(仅建议,不依赖)提示 JVM 触发 GC,加速释放,但System.gc()可能被-XX:+DisableExplicitGC参数禁用。
4. 特殊情况:直接使用 Unsafe 分配:若通过Unsafe.allocateMemory()手动分配直接内存,不会关联Cleaner,必须手动调用Unsafe.freeMemory()释放,否则会造成永久内存泄漏。
四、常见误区澄清
• 误区 1:DirectByteBuffer被 GC 回收 = 直接内存释放?
不成立。DirectByteBuffer的回收是前提,真正释放直接内存的是Cleaner的回调,两者是 “触发与执行” 的关系。
• 误区 2:手动调用buffer.clear()能释放直接内存?
不能。clear()仅重置缓冲区的读写指针,不会释放DirectByteBuffer的强引用,也不会触发直接内存释放。

直接内存的释放流程是怎样的?

直接内存的释放流程核心是 “强引用失效→GC 标记→Cleaner 回调→Native 释放”,全程由 JVM 协同 Cleaner 机制自动完成,具体步骤如下:
1. 前提:DirectByteBuffer 失去强引用
• 应用程序中持有DirectByteBuffer的强引用被销毁(如引用变量置为null、超出作用域)。
• 此时DirectByteBuffer对象成为 GC 可达性分析中的 “不可达对象”,仅保留与Cleaner的虚引用关联。
2. GC 触发并标记可回收对象
• JVM 执行垃圾回收(Minor GC 或 Full GC),扫描到DirectByteBuffer已不可达。
• GC 不会直接回收DirectByteBuffer,而是先检测到其关联的Cleaner(虚引用),并将Cleaner对象加入预设的 “引用队列”(ReferenceQueue)。
3. 后台线程处理引用队列
• JVM 启动时会创建一个优先级最高的后台线程ReferenceHandler,专门循环扫描引用队列。
• 当线程发现队列中新增Cleaner对象时,会调用其clean()方法,触发释放逻辑。
4. 执行 Native 方法释放直接内存
• Cleaner的clean()方法会调用内部关联的Deallocator(释放器)的run()方法。
• Deallocator通过Unsafe.freeMemory()(Native 调用),直接向操作系统申请释放对应的堆外内存(直接内存)。
5. 最终回收堆中 DirectByteBuffer 对象
• 直接内存释放完成后,DirectByteBuffer对象不再有任何引用关联,GC 会在本次或下次回收中,将其从 Java 堆中彻底回收。
• 整个流程结束,直接内存和堆内存均完成释放。
关键补充
• 释放时机依赖 GC:若 GC 未触发,即使DirectByteBuffer不可达,直接内存也不会立即释放,可能出现 “堆内存空闲但直接内存占用高” 的情况。
• 手动辅助释放:可通过buffer = null+System.gc()(仅提示 GC,不保证立即执行)加速流程,但需注意System.gc()可能被-XX:+DisableExplicitGC禁用。
• 手动分配场景:若通过Unsafe.allocateMemory()分配直接内存,无Cleaner关联,需手动调用Unsafe.freeMemory(),否则会造成内存泄漏。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/16 19:49:10

ComfyUI-Manager路径冲突实战:从下载到验证的完整解决方案

ComfyUI-Manager路径冲突实战:从下载到验证的完整解决方案 【免费下载链接】ComfyUI-Manager 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-Manager ComfyUI-Manager作为ComfyUI生态中重要的模型管理工具,在日常使用中经常遇到路径格式…

作者头像 李华
网站建设 2026/1/16 15:19:22

零基础入门:Arduino Uno R3开发板连接心率传感器

从零开始:用Arduino Uno R3玩转心率监测你有没有想过,不用去医院、也不用买几千块的智能手表,就能自己做一个能测心跳的小设备?听起来像科幻片?其实只要一块几十元的开发板和一个传感器模块,再花半小时动手…

作者头像 李华
网站建设 2026/1/16 11:54:23

Godot PCK文件终极解包指南:突破资源提取技术壁垒

Godot PCK文件终极解包指南:突破资源提取技术壁垒 【免费下载链接】godot-unpacker godot .pck unpacker 项目地址: https://gitcode.com/gh_mirrors/go/godot-unpacker 在游戏开发的世界里,Godot引擎以其开源特性和强大功能备受青睐。然而&#…

作者头像 李华
网站建设 2026/1/16 23:22:12

C语言内存函数

在使用内存函数的时候我们需要包含<stdlib.h>的头文件 目录 一 memcpy使⽤和模拟实现 (1)使用 (2)模拟实现 二 memmove使⽤和模拟实现 (1)使用 (2)模拟实现 三 memset函数的使⽤ 四 memcmp函数的使⽤ 一 memcpy使⽤和模拟实现 (1)使用 我们先来查看一下memcp…

作者头像 李华
网站建设 2026/1/17 2:51:38

Zepp Life自动刷步数终极指南:3步搞定微信运动同步

Zepp Life自动刷步数终极指南&#xff1a;3步搞定微信运动同步 【免费下载链接】mimotion 小米运动刷步数&#xff08;微信支付宝&#xff09;支持邮箱登录 项目地址: https://gitcode.com/gh_mirrors/mimo/mimotion 还在为每天凑不够微信运动步数而烦恼&#xff1f;想要…

作者头像 李华
网站建设 2026/1/16 18:48:55

工业设备中RS232引脚功能解析:深度剖析通信标准

工业现场的“老派”通信&#xff1a;为什么我们还在用RS232&#xff1f;你有没有遇到过这种情况——在调试一台新到厂的PLC时&#xff0c;翻遍机身却只找到一个9针的串口&#xff1f;没有网口&#xff0c;也没有USB&#xff0c;只有那根泛黄标签上写着“COM”的线缆。那一刻&am…

作者头像 李华