New Java Feature - Foreign Memory API
概述
- 在讨论代码性能的时候,内存的使用效率是一个绕不开的话题 - Flink/Netty
- 为了避免 JVM GC 不可预测的行为以及额外的性能开销,一般倾向于使用 JVM 之外的内存来存储和管理数据 - 堆外数据 - off-heap data
- 使用堆外存储最常用的办法,是使用 ByteBuffer 来分配直接存储空间 - direct buffer
- JVM 会尽最大努力直接在 direct buffer 上执行 IO 操作,避免数据在本地和 JVM 之间的拷贝
- 频繁的内存拷贝是性能的主要障碍之一
- 为了极致的性能,应用程序通常会尽量避免内存的拷贝
- 理想的情况下,一份数据只需要一份内存空间 - 即零拷贝
ByteBuffer
使用 ByteBuffer 来分配直接存储空间
1 | public static ByteBuffer allocateDirect(int capacity); |
- ByteBuffer 所在的 Java 包是 java.nio,ByteBuffer 的设计初衷是用于非阻塞编程
- ByteBuffer 是异步编程和非阻塞编程的核心类,几乎所有的 Java 异步模式或者非阻塞模式的代码,都要直接或者间接地使用 ByteBuffer 来管理数据
- 非阻塞和异步编程模式的出现,起始于对阻塞式文件描述符(包括网络套接字)读取性能的不满
- 诞生于 2002 年的 ByteBuffer,其最初的设想也主要是用来解决当时文件描述符的读写性能
站在现在的视角重新审视该类的设计,会发现两个主要缺陷
- 缺陷 1 - 没有资源释放的接口
- 一旦一个 ByteBuffer 实例化,它占用内存的释放,会完全依赖 JVM GC
- 使用 direct buffer 的应用,往往需要把所有潜在的性能都挤压出来
- 而依赖于 JVM GC 的资源回收方式,并不能满足像 Netty 这样的类库的理想需求
- 缺陷 2 - 存储空间尺寸的限制
- ByteBuffer 的存储空间的大小,是使用 Java 的 int 来表示的,最多只有 2G - 一个无意带来的缺陷
- 在网络编程的环境下,这并不是一个问题,可是超过 2G 的文件,一定会越来越多
- 2G 以上的文件,映射到 ByteBuffer 上的时候,就会出现文件过大的问题
合理的改进 - 重造轮子 - 外部内存接口
外部内存接口
- 外部内存接口沿袭了 ByteBuffer 的设计思路,但使用了全新的接口布局
- 分配一段外部内存,并且存放 4 个字母 A
1 | try (ResourceScope scope = ResourceScope.newConfinedScope()) { |
- ResourceScope 定义了内存资源的生命周期管理机制,实现了 AutoCloseable 接口,可以使用 try-with-resource 来及时释放掉它管理的内存 - 缺陷 1
- MemorySegment 用于定义和模拟一段连续的内存区域,而 MemoryAccess 用于定义对 MemorySegment 执行读写操作
- 在外部内存接口的设计里,把对象表达和对象操作,拆分成两个类
- 这两类的寻址数据类型,使用的是 long - 缺陷 2
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.