直接内存
JVM 堆内存和外存之间的数据交换需要通过内核缓冲区中转。直接内存(堆外内存)可以跳过这一步,减少一次数据复制,从而提升 I/O 性能。
堆内存 vs 直接内存
堆内存
数据存储在 JVM 堆上,GC 管理生命周期:
直接内存
数据存储在堆外,由操作系统管理:
性能对比
性能测试
测试结果显示:直接内存在文件 I/O 场景下有 10-30% 的性能提升。
直接内存的分配
ByteBuffer.allocateDirect
Netty 的内存池
Netty 使用池化的直接内存,减少分配开销:
直接内存的回收
GC 的影响
直接内存不受 GC 直接管理,但 JVM 会跟踪直接内存的使用。当 GC 回收了 ByteBuffer 对象后,底层的堆外内存会被释放。
JVM 使用 PhantomReference(虚引用)来跟踪 ByteBuffer 对象。当对象被 GC 时,虚引用被加入引用队列,JVM 启动的守护线程会清理堆外内存。
MaxDirectMemorySize
手动清理
直接内存溢出
直接内存泄漏会导致 OutOfMemoryError: Direct buffer memory:
常见原因
泄漏场景:没有释放
Netty 的泄漏检测
Netty 提供了内存泄漏检测:
Netty
处理泄漏
直接内存调优
JVM 参数
监控
通过 JConsole 或 JMX 查看 java.nio.BufferPool.direct 的使用情况。
直接内存使用场景
适合使用直接内存的场景
不适合使用直接内存的场景
本章小结
直接内存的核心要点:
- 存储位置:堆外,不受 GC 直接管理
- I/O 优势:减少 JVM 堆到内核的复制
- 分配开销:比堆内存慢,适合长期使用
- 回收机制:依赖 GC + Cleaner 守护线程
- 监控指标:通过 JMX 监控
DirectByteBuffer数量
延伸思考
为什么 Netty 默认使用直接内存的池化 ByteBuf?
因为 Netty 的典型场景是:
- 高吞吐:每秒处理大量消息,需要池化复用
- 低延迟:每次消息都涉及 I/O,需要直接内存减少复制
- 长连接:连接长时间存在,直接内存可以提前分配并复用
直接内存 + 池化,是高性能网络框架的标配。