Nio-DirectByteBuffer作用

由于Zookeeper中使用了DirectByteBuffer进行IO操作,在此简单介绍下DirectByteBufferHeapByteBuffer的区别.
HeapByteBuffer是在堆上分配的内存,而DirectByteBuffer是在堆外分配的内存,又称直接内存.使用HeapByteBuffer进行IO时,比如调用FileChannel.write(HeapByteBuffer)将数据写到File中时,有两个步骤:

  1. HeapByteBuffer的数据拷贝到DirectByteBuffer
  2. 再从堆外内存将数据写入到文件中.

问题1:为什么要将HeapByteBuffer的数据拷贝到DirectByteBuffer呢?不能将数据直接从HeapByteBuffer拷贝到文件中吗?

并不是说操作系统无法直接访问jvm中分配的内存区域,显然操作系统是可以访问所有的本机内存区域的,但是为什么对io的操作都需要将jvm内存区的数据拷贝到堆外内存呢?是因为jvm需要进行GC,如果io设备直接和jvm堆上的数据进行交互,这个时候jvm进行了GC,那么有可能会导致没有被回收的数据进行了压缩,位置被移动到了连续的存储区域,这样会导致正在进行的io操作相关的数据全部乱套,显然是不合理的,所以对io的操作会将jvm的数据拷贝至堆外内存,然后再进行处理,将不会被jvm上GC的操作影响。

问题2:DirectByteBuffer是相当于固定的内核buffer还是JVM进程内的堆外内存?

 不管是Java堆还是直接内存,都是JVM进程通过malloc申请的内存,其都是用户空间的内存,只不过是JVM进程将这两块用户空间的内存用作不同的用处罢了.Java内存模型如下:

这里写图片描述 

问题3:将HeapByteBuffer的数据拷贝到DirectByteBuffer这一过程是操作系统执行还是JVM执行?

在问题2中已经回答,DirectByteBuffer是JVM进程申请的用户空间内存,其使用和分配都是由JVM进程管理,因此这一过程是JVM执行的.也正是因为JVM知道堆内存会经常GC,数据地址经常移动,而底层通过write,read,pwrite,pread等函数进行系统调用时,需要传入buffer的起始地址和buffer count作为参数,因此JVM在执行读写时会做判断,若是HeapByteBuffer,就将其拷贝到直接内存后再调用系统调用执行步骤2.
代码在sun.nio.ch.IOUtil.write()sun.nio.ch.IOUtil.read()中,我们看下write()的代码:
知乎不能复制,代码地址如下:Java NIO direct buffer的优势在哪儿?(链接地址:https://www.zhihu.com/question/60892134),第一个答案中有代码.

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

 @ETIN(链接地址:https://www.zhihu.com/question/60892134)的答案也说了,如果是使用DirectBuffer就会少一次内存拷贝。如果是非DirectBuffer,JDK会先创建一个DirectBuffer,再去执行真正的写操作。这是因为,当我们把一个地址通过JNI传递给底层的C库的时候,有一个基本的要求,就是这个地址上的内容不能失效。然而,在GC管理下的对象是会在Java堆中移动的。也就是说,有可能我把一个地址传给底层的write,但是这段内存却因为GC整理内存而失效了。所以我必须要把待发送的数据放到一个GC管不着的地方。这就是调用native方法之前,数据一定要在堆外内存的原因。可见,DirectBuffer并没有节省什么内存拷贝,只是因为HeapBuffer必须多做一次拷贝,才显得DirectBuffer更快一点而已。

问题5:在将数据写到文件的过程中需要将数据拷贝到内核空间吗?

需要.在步骤3中,是不能直接将数据从直接内存拷贝到文件中的,需要将数据从直接内存->内核空间->文件,因此使用DirectByteBuffer代替HeapByteBuffer也只是减少了数据拷贝的一个步骤,但对性能已经有提升了.

 

 

你可能感兴趣的:(NIO)