像 TensorFlow、 Ignite、 Flink 以及 Netty 这样的类库,往往对性能有着偏执的追求。为了避免 Java 垃圾收集器不可预测的行为以及额外的性能开销,这些产品一般倾向于使用 JVM 之外的内存来存储和管理数据。这样的数据,就是我们常说的堆外数据(off-heap data)。
使用堆外存储最常用的办法,就是使用 ByteBuffer 这个类来分配直接存储空间(direct buffer)。JVM 虚拟机会尽最大努力直接在直接存储空间上执行 IO 操作,避免数据在本地和 JVM 之间的拷贝。
ByteBuffer缺陷:
- 没有资源释放的接口。一旦一个 ByteBuffer 实例化,它占用了内存的释放,就会完全依赖 JVM 的垃圾回收机制。
- 存储空间尺寸的限制。ByteBuffer 的存储空间的大小,是使用 Java 的整数来表示的。所以,它的存储空间,最多只有 2G。
外部内存接口沿袭了 ByteBuffer 的设计思路,但是使用了全新的接口布局。我们先来看看使用外部内存接口的代码看起来是什么样子的。下面的这段代码,要分配一段外部内存,并且存放 4 个字母 A。
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
MemorySegment segment = MemorySegment.allocateNative(4, scope);
for (int i = 0; i < 4; i++) {
MemoryAccess.setByteAtOffset(segment, i, (byte)'A');
}
}
第一行的 ResourceScope 这个类,定义了内存资源的生命周期管理机制。
第二行的 MemorySegment 这个类,定义和模拟了一段连续的内存区域。
第三行的 MemoryAccess 这个类,定义了可以对 MemorySegment 执行读写操作。
此文章为9月Day14学习笔记,内容来源于极客时间《深入剖析 Java 新特性》