深入剖析 Java 新特性
范学雷
前 Oracle 首席软件工程师,Java SE 安全组成员,OpenJDK 评审成员
16539 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 23 讲
深入剖析 Java 新特性
15
15
1.0x
00:00/00:00
登录|注册

12 | 外部内存接口:零拷贝的障碍还有多少?

你好,我是范学雷。今天,我们来讨论 Java 的外部内存接口。
Java 的外部内存接口这个新特性,现在还在孵化期,还没有发布预览版。我之所以选取了这样一个还处于孵化期的技术,主要是因为这个技术太重要了。我们需要提前认识它;然后在这项技术出来的时候,尽早地使用它。
我们从阅读案例开始,看一看 Java 在没有外部内存接口的时候,是怎么支持本地内存的;然后,我们再看看外部内存接口能够给我们的代码带来什么样的变化。

阅读案例

在我们讨论代码性能的时候,内存的使用效率是一个绕不开的话题。像 TensorFlow、 Ignite、 Flink 以及 Netty 这样的类库,往往对性能有着偏执的追求。为了避免 Java 垃圾收集器不可预测的行为以及额外的性能开销,这些产品一般倾向于使用 JVM 之外的内存来存储和管理数据。这样的数据,就是我们常说的堆外数据(off-heap data)。
使用堆外存储最常用的办法,就是使用 ByteBuffer 这个类来分配直接存储空间(direct buffer)。JVM 虚拟机会尽最大努力直接在直接存储空间上执行 IO 操作,避免数据在本地和 JVM 之间的拷贝。
由于频繁的内存拷贝是性能的主要障碍之一。所以为了极致的性能,应用程序通常也会尽量避免内存的拷贝。理想的状况下,一份数据只需要一份内存空间,这就是我们常说的零拷贝。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java的外部内存接口是一个处于孵化期的新特性,旨在解决ByteBuffer类的两个缺陷:资源释放依赖于垃圾回收机制和存储空间尺寸受限。该接口使用全新的接口布局,通过ResourceScope类管理内存资源的生命周期,解决了ByteBuffer的第一个缺陷。同时,使用MemorySegment和MemoryAccess类对内存执行读写操作,使用长整形寻址类型解决了ByteBuffer的第二个缺陷。外部内存接口的出现超出了预期,为外部函数之间的数据传递提供了新思路,可能会使不同语言间的函数调用变得更简单、更有效率。文章提到,外部内存接口尚处于孵化阶段,但如果正式发布,现有使用ByteBuffer的类库应该可以考虑切换到外部内存接口来获取性能提升。总的来说,本文让读者对外部内存接口有了一个基本的印象,了解了ByteBuffer的两个缺陷以及未来Java要做的改进。同时,文章提出了一个思考题,鼓励读者使用JShell验证资源释放效果。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入剖析 Java 新特性》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(10)

  • 最新
  • 精选
  • 松松
    jshell> try (ResourceScope scope = ResourceScope.newConfinedScope()) { ...> MemorySegment segment = MemorySegment.allocateNative(4, scope); ...> for (int i = 0; i < 4; i++) { ...> MemoryAccess.setByteAtOffset(segment, i, (byte)'A'); ...> } ...> for (int i = 0; i < 4; i++) { ...> System.out.println(MemoryAccess.getByteAtOffset(segment, i)); ...> } ...> } 65 65 65 65 jshell> MemorySegment segment; segment ==> null | 已创建 变量 segment : MemorySegment jshell> try (ResourceScope scope = ResourceScope.newConfinedScope()) { ...> segment = MemorySegment.allocateNative(4, scope); ...> for (int i = 0; i < 4; i++) { ...> MemoryAccess.setByteAtOffset(segment, i, (byte)'A'); ...> } ...> } jshell> for (int i = 0; i < 4; i++) { ...> System.out.println(MemoryAccess.getByteAtOffset(segment, i)); ...> } | 异常错误 java.lang.IllegalStateException:Already closed | at ConfinedScope.checkValidState (ConfinedScope.java:57) | at ScopedMemoryAccess.getByteInternal (ScopedMemoryAccess.java:480) | at ScopedMemoryAccess.getByte (ScopedMemoryAccess.java:470) | at MemoryAccessVarHandleByteHelper.get (MemoryAccessVarHandleByteHelper.java:114) | at MemoryAccess.getByteAtOffset (MemoryAccess.java:105) | at (#6:2) 在 try-with-resource 内阔以访问,否则报 IllegalStateException: Already closed。 如果更严谨些,阔以从 MemoryAddress 判断。

    作者回复: 像是范例,谢谢!

    2021-12-10
    6
  • 小飞同学
    为什么ByteBuffer最大内存是2G?不应该是2^32/2^30=4GB的内存么,没有找到一个合适的解释

    作者回复: 有一位要留给符号,所以只有31位。

    2021-12-14
    3
  • aoe
    知道了大名鼎鼎的ByteBuffer有两个缺陷: 1. 没有资源释放的接口 2. 存储空间尺寸的限制。ByteBuffer 的存储空间的大小,是使用 Java 的整数来表示的。所以,它的存储空间,最多只有 2G。 虽然没有直接使用过ByteBuffer,但依然感觉很厉害的样子。 外部内存接口看起来更厉害啊!

    作者回复: 虽然还在孵化期,还是值得期待的。

    2021-12-10
    2
  • ABC
    老师,我看这两天Log4j2遇到的漏洞就跟JNI有关,可以大概讲一下吗,谢谢.

    作者回复: 这几天太忙了,没顾上看细节。 等我忙过了这一段时间,看看能不能了解一下这个漏洞的技术细节。

    2021-12-13
    1
  • ABC
    我看到外部内存接口在JDK18开启了第二阶段孵化,变化包括: * 在内存访问var句柄中支持更多的载体,例如booleanand MemoryAddress; * 更通用的取消引用 API,可在MemorySegment和MemoryAddress接口中使用; * 一个更简单的 API 来获取向下调用方法句柄,MethodType不再需要传递参数; * 一个更简单的 API 来管理资源范围之间的时间依赖性; * 用于将 Java 数组复制到内存段和从内存段复制的新 API。 到正式发布估计还要一段时间. 有个疑问,想请教一下老师,一般在业务系统中不推荐直接操作外部内存把? 另外还有个有意思的特性,JDK计划在后续版本中删除JDK中的所有finalize()方法.其中最著名的是: java.lang.Object.finalize(). 大概要经历下面的步骤: JDK 的未来版本可能包括以下部分或全部更改: 1. 在使用终结器时在运行时发出警告, 2. 默认情况下禁用终结,可以选择重新启用它, 3. 稍后,删除终结机制,同时保留不起作用的 API, 4. 最后,在迁移大部分代码后,删除上述最终弃用的方法,包括Object::finalize. 我觉得Python自带的IDLE像一个简单的代码编辑器,不清楚JDK后续是否会提供一个类似IDLE的工具.

    作者回复: “第二阶段孵化”,接口会越来越简单。孵化期的接口,验证可行性是一个重要的目标。 “一般在业务系统中不推荐直接操作外部内存吧?”, 这个问题,看我们怎么定义业务系统。一般情况下,会有一层封装,隐藏起来实现的细节。 IDLE这样的工具,很难出现在JDK里。市面已经有非常出色的IDE了,比如IDEA,不再需要另起炉灶了。JDK要解决的,主要还是编程语言和标准类库,其他的事情,应该交给社区和生态来做。

    2021-12-12
    1
  • 周周周文阳 ...
    https://blog.csdn.net/qq_43284469/article/details/126551473 该篇博客除作者名字变更,其他只字未动

    作者回复: 哈哈,这个喜欢吃黄桃的阿昌作者还是一个超级搬运工,把这个专栏的文章搬的差不多了。

    2022-12-18归属地:美国
  • 发光如星
    说实话,jdk11的新特性都不太清楚有哪些,老师有没有这方面的资料

    作者回复: 看JDK 11的release notes

    2022-01-14
  • Jxin
    1.提个问题:为什么新的直接内存管理,将生命周期/数据承接/数据操作拆分成了3个类来实现?分成3个类也行,但使用方感知3个类是不是有点不合适? java因为有jvm对对象做统一的生命周期管理,所以一般java的对象并不包含生命周期这块的逻辑。ResourceScope 的命名其实没有局限在直接内存,所以感觉这个可能是一个公用的独立做对象生命周期管理的玩意。如果是公用的,那么这个独立出来确实是合理的。但是数据承接和数据操作分两个类就有点看不懂了,行为跟着数据走,为啥要拆开?也许行为的实现相对独立,例如迭代器,单独类也合理。但暴露给使用方操作两个类就不合理了,因为操作可以也应该包在MemorySegment内部。或许是为了灵活组合不同操作?在可读性优先的前提下,有时保持愚钝的通过多个子类分隔逻辑,达到隔离场景的效果比灵活更有价值。 2.外部函数之间的数据传递问题,之前考虑过这个问题。服务网格边车模式依赖网络拦截重发实现两个进程间的交互。如果借助外部直接内存,是否可以实现同样的效果?感觉可以,而且可以不再占用网络资源。应该是挺大的提升。但马上又有问题冒出来了,进程间直接内存抢占怎么办?安全问题怎么办?

    作者回复: 第一个问题,虽然我觉得数据和操作拆分各有利弊,现在我还看不到拆分成两个类的优势。也许是为了操作的可扩展性/灵活性,但是现在的设计还没有体现出来需要这样的灵活性。孵化期的API,离最终定版还有很大的一段距离。 第二个问题,这些资源竞争和安全的问题,都可以通过实现细节屏蔽起来。这也是我能想到的第一个问题里类拆分的一个可能性,但是我还没有看到朝着个方向走的样子。

    2021-12-14
    2
  • Ankhetsin
    java和别的不同语言是不是用JNI和JNA?

    作者回复: JNI就是我们说的本地接口。

    2021-12-12
    2
  • ifelse
    理想的状况下,一份数据只需要一份内存空间,这就是我们常说的零拷贝。--记下来
    2022-10-12归属地:浙江
收起评论
显示
设置
留言
10
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部