Java NIO direct buffer的优势在哪儿?

以下是官方文档给出的说明: Given a direct byte buffer, the Java virtual machine will mak…
关注者
207
被浏览
57,436
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

HeapByteBufferDirectByteBuffer的区别在于HeapByteBuffer是在Java Heap上分配的,但是Java NIO在读写到相应的Channel的时候,会先将Java Heap的buffer内容拷贝至直接内存——Direct Memory。这样的话,无疑DirectByteBuffer的IO性能肯定强于使用HeapByteBuffer,它省去了临时buffer的拷贝开销,这也是为什么各个NIO框架大多使用DirectByteBuffer的原因。

具体可以参考 sun.nio.ch.IOUtil#write和sun.nio.ch.IOUtil#read方法验证:

static int write(FileDescriptor fd, ByteBuffer src, long position,
                     NativeDispatcher nd)
        throws IOException
    {
        if (src instanceof DirectBuffer)
            return writeFromNativeBuffer(fd, src, position, nd);

        // Substitute a native buffer
        int pos = src.position();
        int lim = src.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
        try {
            bb.put(src);
            bb.flip();
            // Do not update src until we see how many bytes were written
            src.position(pos);

            int n = writeFromNativeBuffer(fd, bb, position, nd);
            if (n > 0) {
                // now update src
                src.position(pos + n);
            }
            return n;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
    }

————————————

static int read(FileDescriptor fd, ByteBuffer dst, long position,
                    NativeDispatcher nd)
        throws IOException
    {
        if (dst.isReadOnly())
            throw new IllegalArgumentException("Read-only buffer");
        if (dst instanceof DirectBuffer)
            return readIntoNativeBuffer(fd, dst, position, nd);

        // Substitute a native buffer
        ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
        try {
            int n = readIntoNativeBuffer(fd, bb, position, nd);
            bb.flip();
            if (n > 0)
                dst.put(bb);
            return n;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
    }

---------------------------------------------------------------------

为什么要采用C Heap的buffer通过系统调用与kernel交互?我这里简单的说下几个原因(以linux平台为例),欢迎大家补充:

  1. 底层通过write、read、pwrite,pread函数进行系统调用时,需要传入buffer的起始地址和buffer count作为参数。具体参见:write(2): to file descriptorread(2): read from file descriptorpread(2) - Linux man pagepwrite(2) - Linux man page。如果使用java heap的话,我们知道jvm中buffer往往以byte[] 的形式存在,这是一个特殊的对象,由于java heap GC的存在,这里对象在堆中的位置往往会发生移动,移动后我们传入系统函数的地址参数就不是真正的buffer地址了,这样的话无论读写都会发生出错。而C Heap仅仅受Full GC的影响,相对来说地址稳定。
  2. JVM规范中没有要求Java的byte[]必须是连续的内存空间,它往往受宿主语言的类型约束;而C Heap中我们分配的虚拟地址空间是可以连续的,而上述的系统调用要求我们使用连续的地址空间作为buffer。