最近,我研究了JVM详细信息,并遇到了直接内存一词。 @Peter Lawrey的回答引起了我的注意。
所有系统调用(例如读写套接字和文件)仅使用本机内存。他们不能使用堆。
NIO
中进行任何呼叫,我将直接使用 Direct Memory ?答案 0 :(得分:4)
从套接字到套接字的数据传输只能使用本机(非堆)内存是不正确的。它完全取决于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的文章所述,对垃圾收集的影响因收集器而异。