Java安全操作堆外内存应优先使用MemorySegment(JDK 21+正式API),它提供类型化访问、边界检查、作用域自动管理及跨平台兼容性;ByteBuffer.allocateDirect()适用于NIO场景但灵活性低;Unsafe因无GC、无越界检查且已弃用,仅限调试。

Java 提供了 java.nio.ByteBuffer(尤其是 allocateDirect() 创建的直接缓冲区)和底层的 sun.misc.Unsafe(JDK 9+ 中受限但仍可间接使用)来实现堆外内存的直接读写。不过,**JDK 14+ 推荐且更安全的方式是使用 MemorySegment 和 MemoryAccess(属于 Foreign Memory Access API,即 JEP 370 / JEP 393 / JEP 442 等演进成果)**,它已正式进入标准库(jdk.incubator.foreign 在 JDK 17–20,java.lang.foreign 自 JDK 21 起为正式 API)。
使用 JDK 21+ 正式 Foreign Function & Memory API(推荐)
这是目前最规范、跨平台、内存安全且无需反射或非法访问的原生堆外操作方式:
- 通过
MemorySegment.allocateNative()分配未初始化的堆外内存块(单位:字节) - 用
MemoryAccess工具类按类型(如getByte、putInt、getFloatAtOffset)读写指定偏移处的基本类型值 - 支持大小端控制(如
getShortBE/getShortLE),并自动处理对齐与边界检查(可选禁用以提升性能) - 必须显式调用
segment.close()或配合try-with-resources释放内存,避免泄漏
示例(分配 1KB 堆外内存,写入 int、读取 float):
try (MemorySegment segment = MemorySegment.allocateNative(1024, SegmentScope.AUTO)) {
long offset = 0;
MemoryAccess.setIntAtOffset(segment, offset, 123456); // 写入 int(4 字节)
offset += Integer.BYTES;
MemoryAccess.setFloatAtOffset(segment, offset, 3.14f); // 写入 float(4 字节)
int i = MemoryAccess.getIntAtOffset(segment, 0); // 读回 int
float f = MemoryAccess.getFloatAtOffset(segment, Integer.BYTES); // 读回 float
System.out.println(i + ", " + f); // 输出:123456, 3.14
}
使用 ByteBuffer.allocateDirect()(兼容性好,适合简单场景)
适用于已有 NIO 生态、需与 Channel 交互,或仅需基本类型序列化/反序列化的场景:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 调用
ByteBuffer.allocateDirect(size)获取直接缓冲区,其 backing memory 即堆外地址 - 通过
putInt()、getDouble()等方法读写,内部自动维护 position、limit,并处理字节序(默认大端,可用order(ByteOrder.LITTLE_ENDIAN)切换) - 不提供任意偏移随机访问 —— 所有操作基于当前 position;若需跳转,用
position(int)手动设置 - 无法直接获取原始地址,也不暴露底层指针,安全性高但灵活性较低
示例:
ByteBuffer bb = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder());
bb.putInt(123456).putFloat(3.14f);
bb.rewind(); // 重置 position 到 0
int i = bb.getInt(); // → 123456
float f = bb.getFloat(); // → 3.14f
慎用 Unsafe(仅限高级调试或框架开发)
Unsafe 可绕过 JVM 安全检查直接读写任意地址,但风险极高:
- JDK 9+ 默认禁止反射获取
Unsafe实例;需添加--add-opens java.base/jdk.internal.misc=ALL-UNNAMED启动参数 - 分配内存用
allocateMemory(),释放必须手动调用freeMemory(),无自动回收机制 - 读写需自行计算字节偏移(如
getInt(address + 8)),且不校验内存是否有效或越界 —— 崩溃风险极大 - 类型大小、对齐、字节序全部由开发者保证,极易出错;不同 JVM 实现行为可能不一致
不建议业务代码使用,仅作理解底层机制参考。
关键注意事项
-
内存生命周期必须严格管理:Native 内存不会被 GC 回收,忘记
close()或freeMemory()将导致内存泄漏 -
类型大小与对齐需明确:例如
long通常占 8 字节且常要求 8 字节对齐,错位访问在某些平台会抛IllegalStateException或性能下降 -
避免跨线程共享未同步的 native 内存段:Foreign API 的
MemorySegment默认非线程安全,多线程并发读写需额外同步 -
优先使用封装好的视图:如
segment.asSlice(offset, size).asByteBuffer()可桥接到传统 NIO 流程,兼顾安全与互操作性

















