读写套接字和文件仅使用本机内存?

时间:2019-03-17 07:53:06

标签: java java-8 jvm java-native-interface nio

最近,我研究了JVM详细信息,并遇到了直接内存一词。 @Peter Lawrey的回答引起了我的注意。

  

所有系统调用(例如读写套接字和文件)仅使用本机内存。他们不能使用堆。


我的问题

  1. 这是真的吗?
  2. 在哪里可以整理此声明?
  3. 这是否意味着在NIO中进行任何呼叫,我将直接使用 Direct Memory

1 个答案:

答案 0 :(得分:4)

套接字传输(和系统调用)中的本地内存与堆内存

从套接字到套接字的数据传输只能使用本机(非堆)内存是不正确的。它完全取决于JVM的实现,甚至在同一实现中有时可能会有所不同。

实际上,编写直接使用堆内存并避免复制的JNI函数相当容易。 JNI API提供了对Java堆中的数据进行零复制访问的方法:

第二个在处理字节数组时非常有用。

与垃圾回收的交互作用

这些JNI函数may prevent garbage collection from making progress。通常,进行复制会更有利。在进行阻塞的系统调用时(例如,当不确定知道内核已缓冲要返回的数据时,例如从TCP套接字读取),尤其如此。在其他情况下,有可能逐步处理较小的阵列,以避免长时间停顿和复制。

由于这些挑战,即使在内核中不会发生阻塞并且存在阻塞的情况下,OpenJDK 11中的当前实现也不会尝试向堆分配的(非直接)字节缓冲区进行零拷贝传输。不受无限延迟的影响,对垃圾回收没有影响。

使用直接字节缓冲区的禁忌

在NIO中使用直接字节缓冲区存在不同的问题:这些缓冲区需要某种类型的终结处理。结果,垃圾收集器无法像其他对象一样高效(迅速)处理它们。通常,仅当直接字节缓冲区寿命长(例如,与使用它们的通道一起分配)时,才应谨慎使用。对于临时缓冲区,大多数情况下,由数组支持的缓冲区(或纯数组)都比较好。

OpenJDK实现通过将直接缓冲区与当前线程相关联,透明地将它们用于通道上的传输,然后将它们返回到每个线程缓存中以备将来使用,从而避免了此问题。这样,直接缓冲区就不会不断分配和丢弃。

较旧的OpenJDK版本

上面提到的关键部分数组访问功能可以追溯到Java 1.2。当然,还不确定单个虚拟机和垃圾回收实现是否仍会创建临时副本(对接口进行了精心设计,避免了实现副本无需复制)。在OpenJDK 8的Hotspot中,这些JNI函数从不进行复制,但是正如AlekseyShipilёv的文章所述,对垃圾收集的影响因收集器而异。