ByteBuffer.allocate()与ByteBuffer.allocateDirect()

allocate()allocateDirect() ,这是问题。

多年以来,我只是坚持这样一种想法,即由于DirectByteBuffer是在操作系统级别的直接内存映射,它比HeapByteBuffer更快地执行get / put调用。 直到现在,我从来没有真正有兴趣find有关情况的确切细节。 我想知道两种types的ByteBuffer的哪一种更快,以及在什么条件下。

Ron Hitches在他出色的Java NIO书中似乎提供了我认为可以很好的回答你的问题:

操作系统在内存区域执行I / O操作。 就操作系统而言,这些内存区域是连续的字节序列。 因此,只有字节缓冲区才有资格参与I / O操作,这并不奇怪。 还要回想一下,操作系统将直接访问进程的地址空间,在这种情况下是JVM进程,以传输数据。 这意味着作为I / O对象目标的内存区域必须是连续的字节序列。 在JVM中,字节数组可能不会连续存储在内存中,或者垃圾收集器可以随时移动它。 数组是Java中的对象,数据存储在对象内部的方式因JVM实现而异。

为此,介绍了直接缓冲区的概念。 直接缓冲区用于与通道和本地I / O例程交互。 他们尽最大努力将字节元素存储在通道可用于直接或原始访问的内存区域中,通过使用本机代码来指示操作系统直接排空或填充内存区域。

直接字节缓冲区通常是I / O操作的最佳select。 通过devise,它们支持JVM可用的最高效的I / O机制。 非本地字节缓冲区可以传递给通道,但这样做可能会导致性能损失。 非直接缓冲区通常不可能成为本地I / O操作的目标。 如果您将非直接的ByteBuffer对象传递给要写入的通道,则通道可能会在每次调用时隐式执行以下操作:

  1. 创build一个临时直接的ByteBuffer对象。
  2. 将非直接缓冲区的内容复制到临时缓冲区。
  3. 使用临时缓冲区执行低级I / O操作。
  4. 临时缓冲区对象超出范围并最终被垃圾收集。

这可能会导致缓冲区复制和每个I / O上的对象stream失,这正是我们想要避免的事情。 但是,根据实施情况,情况可能不会那么糟糕。 运行时可能会caching和重用直接缓冲区或执行其他聪明的技巧来提高吞吐量。 如果您只是创build一次性使用的缓冲区,则差异不显着。 另一方面,如果您将在高性能场景中重复使用缓冲区,则最好分配直接缓冲区并重新使用它们。

直接缓冲区对于I / O来说是最佳的,但是它们可能比非直接字节缓冲区更昂贵。 直接缓冲区使用的内存是通过调用本地操作系统特定的代码来分配的,绕过了标准的JVM堆。 根据主机操作系统和JVM实现情况,设置和拆除直接缓冲区可能比堆驻留缓冲区贵得多。 直接缓冲区的内存存储区域不受垃圾回收处理,因为它们不在标准的JVM堆中。

使用直接缓冲区和非直接缓冲区的性能权衡取决于JVM,操作系统和代码devise。 通过在堆外部分配内存,您可能会使应用程序受到JVM不知道的其他外力的影响。 当发挥更多的运动部件,确保你达到预期的效果。 我推荐以前的软件格言:首先让它工作,然后让它快速。 先不要太在意优化, 首先要关注正确性。 JVM实现可能能够执行缓冲区caching或其他优化,这些优化将为您提供所需的性能,而无需您付出大量不必要的努力。

没有理由期望直接缓冲区在jvm中访问更快。 当你将它们传递给本地代码时,它们的优势就来了 – 比如各种渠道背后的代码。

最好做你自己的测量。 快速回答似乎是从allocateDirect()缓冲区发送的时间比allocate()variables(根据大小复制文件testing的时间allocate()less25%到75%,但是分配本身可以要慢得多(甚至是100倍)。

资料来源:

  • 为什么ByteBuffer.allocate()和ByteBuffer.allocateDirect()之间的奇数性能曲线差异

  • ByteBuffer.allocateDirect太可笑了

  • 何时使用arrays,缓冲区或直接缓冲区

因为DirectByteBuffers是操作系统级别的直接内存映射

他们不是。 它们只是普通的应用程序内存,但在Java GC中不会受到重新定位的影响,从而大大简化了JNI层中的内容。 你所描述的适用于MappedByteBuffer

它会执行更快的get / put调用

这个结论并不是从这个前提出发的。 前提是错误的; 而且结论也是错误的。 一旦进入JNI层,它们会更快,如果您正在读取和写入同一个DirectByteBuffer ,则速度会更快,因为数据永远DirectByteBuffer跨越JNI边界。