java 为什么DirectByteBuffer有更高的分配和释放成本?

z4iuyo4d  于 2023-08-01  发布在  Java
关注(0)|答案(2)|浏览(117)

ByteBuffer的java文档中提到,由allocateDirect分配的直接缓冲区具有更高的分配和释放成本。
直接字节缓冲区可以通过调用该类的allocateDirect工厂方法来创建。此方法返回的缓冲区通常比非直接缓冲区具有稍高的分配和释放成本。
我想知道为什么会有更高的成本?在引擎盖下需要做什么?
由于mmap的成本,我可以理解MappedByteBuffer的成本较高。但是在我看来,DirectByteBuffer只是JVM进程中的一个内存块,就像Java堆一样。它们都在JVM进程堆中。
我不知道在java堆内部或外部分配或释放内存块有什么区别。

rxztt3cl

rxztt3cl1#

取决于很多因素,它更有可能导致显式的malloc调用,而不仅仅是.allocate,尽管它取决于OS+Arch,并且它很可能是.allocate的别名。
一些背景和细节,以防答案不是特别令人满意:
参见this answer了解一些上下文。
通常,java中的对象(特别是字节数组,它是基本HeapByteBuffer的底层数据存储,由.allocate创建)具有以下属性:

  • 它们被创建并保留在JVM堆内存中。
  • 垃圾收集器将检查它们并跟踪它们,并将它们四处移动。
  • 数组实际上并不一定是连续的。JVM会尝试,但规范中没有规定JVM必须将字节保持在一起。垃圾收集器再次发挥作用:如果堆中有两大块空闲内存,并且您分配了一个比任何一个切片都大的字节数组,则GC将不得不将其全部移动,或者抛出异常,或者允许数组被“拆分”以避免移动内容。我不确定是否存在真正做到这一点的JVM。
  • 内存也是JVM进程的一部分;通常,就OS而言,整个堆(无论它们是否包含活动对象)被底层OS认为是“已用内存”。

使用.allocateDirect,您可以打破一系列规则:

  • 他们是被创造出来的。存在于堆之外(例如将是除了-Xmx之外的参数,作为示例,该参数用于设置最大堆大小)。
  • 据推测,这个 always 会导致一个操作系统级的malloc调用,向操作系统请求一个连续的ram块。malloc调用可能需要时间。
  • 这个块实际上是连续的,因为malloc创建了它。
  • 块永远不会移动。

请注意,这里的理论存在一些漏洞-如果它显式地为malloc艾德,那么它应该显式地为free d,但ByteBuffer既没有deallocate()方法,也不是(Auto)Closable。另外,javadoc保留了很多权利;例如,.allocateDirect可能allocate没有什么不同-JVM可以自由地使用堆 * 或不使用 *。这些直接的操作系统级交互往往会这样做:Java必须在各种OS+Architecture组合上运行。如果一些OS+arch组合没有直接缓冲区,现在怎么办?.allocateDirect是否应该快速失败(抛出异常)?这将是明智的,除非,只有当规范明确锁定了直接字节缓冲区可以保证什么,这就存在一个问题,因为在OS+Arch组合中有大量的变化。
“不移动”是各种低级I/O OS内核调用的要求(在这种情况下,您告诉内核:请直接告诉系统中的网络硬件将传入的字节直接复制到这个内存块中-这种低级I/O。并非所有操作系统都支持它,也并非所有操作系统都以相同的方式支持它)。一个普通的堆缓冲区不能使用它;如果底层操作系统支持它,JVM也支持它,JVM必须建立自己的直接缓冲区(在堆之外/在与GC隔离的区域中,以确保它不会移动),并启动一个单独的进程将这些字节blit到您的缓冲区中,考虑到GC系统可以四处移动。相反,如果你问例如:一个FileChannel对象将字节从文件复制到你的直接缓冲区,它可能是可能的,你的JVM的原生impl支持FileChannel只会告诉操作系统告诉SSD * 直接 * 这样做,没有来自操作系统/CPU的任何交互。有些硬件可以做到这一点。但仅限于“固定”存储器位置。
你的JVM是否真的能做到这一切--没有保证。但是如果可以的话,它只能在你做一个直接缓冲区的时候才能做到。

wfveoks0

wfveoks02#

分配一个非直接的ByteBuffer可以被垃圾收集器完全优化,因此它可能比分配一个直接的ByteBuffer更有效。在实践中,差异可能并不那么显著。
更大的问题是去配置。这两种类型的ByteBuffers只能通过垃圾回收来释放,但直接ByteBuffers通常需要更昂贵的旧代回收。因此,不建议使用短期直接ByteBuffers,因为它会增加GC负载。
请注意,有一种不受支持的方法可以显式删除直接ByteBuffers,但是在java.lang.foreign API完全发布之后,这种功能将在某个时候消失。如果你需要短期的堆外缓冲区,那么应该使用MemorySegments

相关问题