JVM--直接内存(Direct Memory)
JVM–直接内存(Direct Memory)
背景
-
JVM–直接内存(Direct Memory)
-
博主以黑马JVM进行学习
定义
-
直接内存(Direct Memory):
-
不属于 JVM 运行时数据区(堆 / 栈 / 方法区等),而是操作系统的本地内存(用户态的直接内存);
-
核心场景:NIO 操作时的
ByteBuffer.allocateDirect()会分配直接内存,作为数据缓冲区; -
特性:分配 / 回收的成本高(需调用系统调用),但读写性能高(减少内存复制);
-
管理:不受 JVM 垃圾回收(GC)直接管理,JVM 堆 GC 不会主动回收直接内存;
-
核心 API:
java.nio.ByteBuffer(allocateDirect()分配直接内存,allocate()分配堆内存)。
-
为什么使用了直接内存,读写文件的效率会变高?
- java不能直接调用系统内存,有专门的java堆内存,java可以直接访问direct memory,少了一次复制操作
- 总之就是减少 “内存复制” 次数,对比堆内存和直接内存的文件读写流程:
- 堆内存(Heap Buffer)读写文件流程:
- 缺陷:数据需从「内核缓冲区 → 堆内存 → Java 程序」,经历两次复制;
- 额外问题:堆内存中的 ByteBuffer 对象是 GC 管理的,若数据量大,会触发 GC,且堆内存到内核缓冲区的复制是 “冗余操作”。

- 直接内存(Direct Buffer)读写文件流程:
- 直接内存是「用户态」和「内核态」共享的内存区域,数据从内核缓冲区直接映射到直接内存,仅一次复制;
- Java 程序可直接访问直接内存(无需经过 JVM 堆的复制),大幅提升 IO 效率;
- 避免堆内存频繁 GC 对 IO 性能的影响。

直接内存溢出
-
OutOfMemoryError: Direct buffer memory
-
溢出原因:
- 直接内存有上限(默认与堆最大值
-Xmx一致,可通过-XX:MaxDirectMemorySize设置); - 频繁分配直接内存但未及时释放,导致超出上限;
- 禁用显式 GC(
-XX:+DisableExplicitGC)后,Direct ByteBuffer 的 Cleaner 机制失效,直接内存无法及时回收。
- 直接内存有上限(默认与堆最大值
直接内存的释放原理
- 分配内存
1 | Unsafe unsafe = getUnsafe(); |
- 释放内存
1 | unsafe.freeMemory(base); |
-
不是通过gc管理,而是根据Unsafe这个底层类进行管理的
-
Unsafe类:干分配内存,释放内存的活
-
借用了java中虚引用机制
-
结论:
- 使用了Unsafe对象完成了直接内存的分配回收,并回收需要主动调用freeMemory方法
- ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
禁用显示回收对直接内存的影响
-
-XX:+DisableExplicitGC 禁用显示回收
-
代码中System.gc()就是一种显示的垃圾回收,会触发Full GC(牺牲性能),而 Full GC 时会清理堆中无用的 Direct ByteBuffer 对象,进而触发 Cleaner 释放直接内存;
-
解决方法:
- 避免使用
ByteBuffer.allocateDirect(),改用堆内存(ByteBuffer.allocate()); - 手动通过 Unsafe.freeMemory () 释放直接内存(不依赖 GC);
- 保留显式 GC(不设置
-XX:+DisableExplicitGC),牺牲少量性能换取内存安全。
- 避免使用
-
对比
| 维度 | 直接内存 | 堆内存 |
|---|---|---|
| 归属 | 操作系统本地内存 | JVM 运行时数据区 |
| GC 管理 | 不直接管理(依赖 Cleaner) | 直接管理(Minor/Major GC) |
| 分配 / 回收成本 | 高(系统调用) | 低(JVM 内部操作) |
| 读写性能 | 高(少一次复制) | 低(多一次复制) |
- Direct ByteBuffer 的内存占用:
- Direct ByteBuffer 对象本身存储在堆中(仅占少量内存,记录直接内存的地址);
- 真正的大数据存储在直接内存(操作系统本地内存)
- JVM 堆 GC 回收的是 Direct ByteBuffer 对象,而非直接内存本身。
总结
- 直接内存是操作系统本地内存,不属于 JVM 堆,NIO 中使用可减少内存复制,提升 IO 效率;
- 直接内存的分配依赖 Unsafe.allocateMemory (),释放依赖「Cleaner(虚引用)+ ReferenceHandler 线程 + Unsafe.freeMemory ()」;
- 直接内存溢出会报
Direct buffer memory,可通过-XX:MaxDirectMemorySize调整上限; -XX:+DisableExplicitGC会导致 Cleaner 机制失效,需手动释放直接内存或保留显式 GC;- 直接内存分配 / 回收成本高但读写性能高,适合大文件 / 高并发 IO 场景。