套接字传输(和系统调用)中的本地内存与堆内存
往返套接字的数据传输只能使用本机(非堆)内存是不正确的.它完全取决于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的文章所述,垃圾收集器的影响因收集器而异.