直接内存
直接内存是 JVM 堆外内存的重要组成部分。它不是 Java 堆的一部分,但在 NIO、Netty、大文件处理和高性能网络中非常常见。
# 1. 什么是直接内存
直接内存指 JVM 通过本地方法在堆外分配的内存。
典型使用方式:
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
直接内存不受 Java 堆大小直接限制,但会占用进程内存。
# 2. 为什么使用直接内存
传统 IO 中,数据可能在堆内数组和 native 内存之间复制。
直接内存可以减少某些场景下的数据复制,特别是和操作系统 IO 交互时。
适合:
- 高频网络 IO。
- 大文件读写。
- Netty ByteBuf。
- NIO Channel。
# 3. 直接内存的释放
直接内存不是普通 Java 对象内存。DirectByteBuffer 对象在堆上,但它引用的真实内存在堆外。
当 DirectByteBuffer 对象被回收时,Cleaner 机制会释放对应直接内存。
这意味着:
- 直接内存释放不一定及时。
- 大量短生命周期直接内存可能造成压力。
- 生产中应尽量复用直接缓冲区。
# 4. MaxDirectMemorySize
可以通过参数限制直接内存大小:
-XX:MaxDirectMemorySize=512m
直接内存不足时可能出现:
java.lang.OutOfMemoryError: Direct buffer memory
# 5. 堆外内存不只直接内存
进程内存不等于 Java 堆。
JVM 进程内存可能包括:
- Java 堆。
- 元空间。
- 线程栈。
- 直接内存。
- Code Cache。
- GC 内部数据结构。
- native 库内存。
- mmap 文件映射。
容器环境下尤其要注意总内存,而不能只看 -Xmx。
# 6. 常见问题
直接内存问题常见于:
- Netty 缓冲区泄漏。
- 大量创建 direct buffer。
- 没有正确释放引用。
- 容器内存限制过小。
-Xmx设置过大,挤压堆外空间。
排查时要结合 native memory tracking、进程 RSS、堆外指标和框架自带泄漏检测。
# 7. 阶段小结
直接内存能提升 IO 场景性能,但它不受 Java 堆直观管理,释放也不如堆对象直接。高性能系统使用直接内存时,要同时管理堆、堆外、线程栈和容器总内存。
# 8. DirectByteBuffer 结构关系
Java 堆
┌────────────────────────┐
│ DirectByteBuffer 对象 │
│ ├─ address ------------┼─────► 堆外直接内存
│ ├─ capacity │ ┌──────────────┐
│ └─ cleaner │ │ native memory │
└────────────────────────┘ └──────────────┘
DirectByteBuffer 对象本身在 Java 堆上,真正的数据在堆外。只有当堆上的 DirectByteBuffer 不再可达并被回收后,Cleaner 才有机会释放堆外内存。
# 9. 堆外内存为什么容易误判
很多监控只看 Java 堆:
堆使用率 40%
容器内存 95%
这时问题可能在:
- 直接内存。
- 线程栈。
- 元空间。
- mmap。
- native 库。
- JVM 内部结构。
所以容器环境必须看 RSS、cgroup memory、NMT,而不能只看 heap usage。
# 10. Netty 中的直接内存
Netty 常使用直接内存提升网络 IO 性能。
需要关注:
- ByteBuf 是否 release。
- 是否开启 leak detector。
- PooledByteBufAllocator 指标。
MaxDirectMemorySize。- 容器总内存。
如果 ByteBuf 引用计数没有正确释放,可能导致堆外内存泄漏。
# 11. Native Memory Tracking
可以用 NMT 观察 JVM 本地内存。
启动参数:
-XX:NativeMemoryTracking=summary
查看:
jcmd <pid> VM.native_memory summary
NMT 有一定开销,生产环境是否开启要结合实际评估。
# 专家速查表
| 使用来源 | 典型场景 | 风险 |
|---|---|---|
ByteBuffer.allocateDirect | NIO 直接缓冲区 | 释放不及时 |
| Netty direct buffer | 网络高性能读写 | 池化配置不当 |
| mmap | 文件映射 | 文件占用和刷盘一致性 |
| JNI/native 库 | 本地内存分配 | JVM 堆外不可见 |
| 压缩/加密库 | 本地临时缓冲 | 峰值难估算 |
直接内存问题往往不是 Java 堆 dump 能完全解释的。排查时要结合 NMT、进程 RSS、容器内存限制、直接内存上限和框架池化指标。
# 12. Tips 快问快答
Q:直接内存受 -Xmx 限制吗?
A:不直接受 -Xmx 限制,但受进程总内存、MaxDirectMemorySize 和系统资源限制。
Q:直接内存一定比堆内存快吗?
A:不是。它在和 native IO 交互时可能减少复制,但分配释放成本更高,适合复用和大块 IO。
Q:为什么堆 dump 看不到堆外大块数据?
A:heap dump 主要记录堆对象。能看到 DirectByteBuffer 对象,但真实堆外内存不作为普通堆对象展示。
Q:直接内存泄漏怎么查?
A:结合 NMT、框架指标、DirectByteBuffer 数量、Netty leak detector 和进程 RSS 趋势。