最近我进入了JVM细节并遇到了一个术语直接内存。 @Peter Lawrey的回答引起了我的注意。
所有系统调用(如读取和写入套接字和文件)仅使用本机内存。他们不能使用堆。
NIO
中的任何调用,我将直接使用直接内存?从套接字到套接字的数据传输不能仅使用本机(非堆)内存。它完全取决于JVM实现,甚至可以在同一实现中不时变化。
事实上,编写直接使用堆内存并避免复制的JNI函数相当容易。 JNI API提供了对Java堆中数据进行零拷贝访问的方法:
第二个在处理字节数组时非常有用。
这些JNI函数may prevent garbage collection from making progress。通常,制作副本更有利。在进行阻塞系统调用时尤其如此(例如,当确定内核已缓冲数据要返回时,从TCP套接字读取)。在其他情况下,可以以较小的片段递增地处理数组,以避免长时间停顿和需要复制。
由于这些挑战,OpenJDK 11中的当前实现不会尝试与堆分配(非直接)字节缓冲区进行零拷贝传输,即使在内核中不会发生阻塞且不会对其产生任何影响的情况下也是如此。垃圾收集由于无限制的延迟。
在NIO中使用直接字节缓冲区有不同的问题:这些缓冲区需要某种终结。结果,垃圾收集器不能像其他对象那样有效(和迅速)地处理它们。通常,只有在它们是长寿命的情况下才使用直接字节缓冲区是谨慎的(例如,与使用它们的通道一起分配)。对于临时缓冲区,阵列支持的缓冲区(或普通数组)在大多数情况下都是优越的。
OpenJDK实现通过将直接缓冲区与当前线程相关联来避免此问题,透明地将它们用于通道上的传输,然后将它们返回到每线程缓存以供将来使用。这样,不会不断地分配和丢弃直接缓冲区。
上面提到的关键部分数组访问函数可以追溯到Java 1.2。当然,未指定单个虚拟机和垃圾收集实现是否仍然是临时副本(接口经过精心设计,以避免副本实现它们)。在OpenJDK 8中的Hotspot中,这些JNI函数永远不会复制,但正如AlekseyShipilёv的文章中所解释的那样,垃圾收集的影响因收集器而异。