Java ByteBuffer 与 HeapByteBuffer 的区别


HeapByteBuffer

HeapByteBuffer分配在Java Heap内。

当向NIO Channel写入HeapByteBuffer数据时,需要先把HeapByteBuffer数据拷贝到DirectMemory后,才能把数据发送出去。

Usage

1
2
//  capacity为字节的数量
ByteBuffer.allocate(int capacity);

DirectByteBuffer

DirectByteBuffer分配在Java Heap外。

当向NIO Channel写入DirectByteBuffer数据时,不需要拷贝,直接把数据发送出去。这样的话,无疑DirectByteBuffer的IO性能肯定强于使用HeapByteBuffer,因为它省去了临时buffer的拷贝开销,这也是为什么各个NIO框架大多使用DirectByteBuffer的原因。

Usage

1
2
3
4
5
6
7
8
9
//  可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,
// 不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,
// 其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。
ByteBuffer.allocateDirect(int capacity);

// free
if (byteBuffer.isDirect()) {
((DirectBuffer)byteBuffer).cleaner().clean();
}

QA

Direct buffer是相当于固定的内核buffer还是JVM进程内的堆外内存?

JVM进程的Java堆外申请的内存,是用户空间的,DirectByteBuffer的创建就是使用了malloc申请的内存。

为什么在执行网络IO或者文件IO时,一定要通过堆外内存呢?

Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer’s content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system’s native I/O operations.

如果是使用DirectBuffer就会少一次内存拷贝。如果是非DirectBuffer,JDK会先创建一个DirectBuffer,再去执行真正的写操作。

这是因为当我们把一个地址通过JNI传递给底层的C库的时候,有一个基本的要求:就是这个地址上的内容不能失效。然而,在GC管理下的对象是会在Java堆中移动的。也就是说,有可能我把一个地址传给底层的write,但是这段内存却因为GC整理内存而失效了。所以我必须要把待发送的数据放到一个GC管不着的地方。这就是调用native方法之前,数据一定要在堆外内存的原因。

此外,使用DirectBuffer好处还有,GC压力更小。虽然GC仍然管理着DirectBuffer的回收,但它是使用PhantomReference(虚引用)来达到的,在平常的Young GC或者mark and compact的时候却不会在内存里搬动。如果IO的数量比较大,比如在网络发送很大的文件,那么GC的压力下降就会很明显。

需要特别注意的是,DirectBuffer直接分配在JVM之外的物理内存,而不是JVM中的逻辑内存,需要往Socket或其他接口写的时候,不需要将数据从JVM复制到物理内存,直接输出即可。缺点是当你需要对这些数据进行额外处理的时候,如编码,过滤等,数据还是会复制到 JVM,所以请确保你不需要对数据进行这些额外操作,只是从一个文件复制数据到另一个文件,一个Socket到另一个的时候才使用。


Reference

https://blog.csdn.net/xieyuooo/article/details/7547435
https://www.zhihu.com/question/60892134