1、关于Java的DirectBuffer参考知乎问答在JavaNIO中,关于DirectBuffer和HeapBuffer的问题?DirectBuffer的对象本身是在堆中的,但它指的是一块非堆本机内存。这段内存其实属于java进程的内存。从这个角度看,DirectBuffer是在与用户空间相关的内存中。DirectBuffer的优点是它减少了从jvm堆到本机内存的复制操作。下面会有一些热点源码,大家可以更直观的感受一下;copy原因:堆中有GC,对象地址可能移动;除了CMS标记排序,其他垃圾回收算法都会做copyandmove排序;还有一个问题是,如果涉及到操作系统的底层操作,nativememory中的数据必须复制到各种内核缓冲区(kernelbuffers,如socketbuffers和PageCache)。2.java网络传输文件示例一般情况下通过java发送本地文件的一般流程如下图所示:3.java中这些复制操作的优化【零复制】1.DirectBuffer“上面提到的”简单的DirectBuffer是其实不算零拷贝,直接内存和零拷贝还是两个概念。只是在零拷贝的很多概念中都用到了直接内存。DirectBuffer只是减少了一份从C堆到java堆的拷贝。零拷贝更多的是指操作系统底层的一些实现。java[FileInputStream]在读取本地文件的过程中,会调用native方法readBytes()。下面是hotspot关于readBytes()的源码,可以看到将C数组复制到java数据的过程:env,jobjectthis,jbyteArraybytes,jintoff,jintlen){returnreadBytes(env,this,bytes,off,len,fis/fd)}/jdk/src/share/native/java/io/io_util.c/复制代码**堆栈分配缓冲区的最大大小。*可以在堆栈上分配的最大缓冲区大小*/#defineBUF_SIZE8192jintreadBytes(JNIEnv*env,jobjectthis,jbyteArraybytes,jintoff,jintlen,jfieldIDfid){jintnread;charstackBuf[BUF_SIZE];//BUF_SIZE=8192字符*buf=NULL;fd;//传入的Java字节数组不能为nullif(IS_NULL(bytes)){JNU_ThrowNullPointerException(env,NULL);返回-1;}//off,len参数是否越界if(outOfBounds(env,off,len,bytes)){JNU_ThrowByName(env,"java/lang/IndexOutOfBoundsException",NULL);返回-1;}//如果要读取的长度为0,直接将读取的长度返回0if(len==0){return0;}elseif(len>BUF_SIZE){//如果要读取的长度大于BUF_SIZE,则不能在栈上分配空间,需要在堆上分配空间buf=malloc(len);if(buf==NULL){//malloc分配失败,抛出OOM异常JNU_ThrowOutOfMemoryError(env,NULL);返回0;}}else{buf=stackBuf;}//获取FileDescriptor中记录的文件描述符fd=GET_FD(this,fid);if(fd==-1){JNU_ThrowIOException(env,"StreamClosed");nread=-1;}else{//调用IO_Read读取nread=IO_Read(fd,buf,len);if(nread>0){//读取成功后,将数据从buf复制到Java字节数组(*env)->SetByteArrayRegion(env,bytes,off,nread,(jbyte*)buf);}elseif(nread==-1){//读取系统调用返回-1,因为读取失败JNU_ThrowIOExceptionWithLastError(env,"Readerror");}else{/*EOF*///操作系统读取并读取返回0,即认为读取结束,Java中返回-1认为是读取结束nread=-1;}}//如果你正在使用堆空间(len>BUF_SIZE),你需要手动释放它if(buf!=stackBuf){free(buf);}返回nread;}??2。MappedByteBuffer对应Linux底层API:mmap()使用nativememory“userbuffer”映射一个DirectByteBuffer,然后使用底层mmap技术(memorymap)将java进程中的这个nativememory映射到内核文件的地址inbuffer实际上减少了一次read()系统调用,也就是少了一次从用户态到内核态的切换。《两个系统调用:1、mmap2、write》原来的本地文件读写操作流程是:【磁盘->内核缓冲区->用户缓冲区【如果nativememory是directmemory,则没有copytoheap操作,native内存其实可以理解为C语言的堆,所以所有的native方法调用都会涉及到nativememory]??->jvmheap,写操作相反]使用mmap后的读写过程:[disk->kernelbuffer->disk],如果将磁盘文件发送到网络,流程如下:[disk->kernelbuffer->socketbuffer->networkinterface]3.NIOChanneltransferTo&transferFrom对应Linux底层API:sendfile()使用底层sendfile()技术,即在内核态发起系统调用,完成所有数据传输。向网络发送磁盘文件:disk->kernelbuffer->socketbuffer->networkinterfaceLinux2.1sendfile():4.NIOChannelPipe对应Linux底层API:splice()Linux2.6.17splice()其他参考文章:LinuxI/O原理和Zero-copy技术全揭晓
