ByteBuffer 是 java.nio 包下提供的一个类,提供了堆内内存分配与堆外内存分配机制,堆内内存分配方式:ByteBuffer.allocate(size)分配大小为size的字节数组;堆外内存分配方式:ByteBuffer.allocateDirect(size), 在堆外内存空间分配大小为size的空间地址。ByteBuffer.allocateDirect 返回的是一个DirectByteBuffer对象。
ByteBuffer 的一些基本用法实例:
堆外内存称为直接内存,那么这块区域到底指向什么地方?查看DirectByteBuffer源码部分unsafe.allocateMemory(size); unSafe 表示native
内存,native内存表示操作系统内存(文件读写流程需要经过应用内存->内核内存->文件,所以这样认为),native是在jvm之外的内存,因此native区域的内存释放是不受JVM的控制, 但是也会通过参数-XX:MaxDirectMemorySize 控制native内存的大小,很多情况设置成为JVM堆内存的大小,当申请native内存不够时就会发生JVM FULL GC清除一部分空间,如果还是不够就会调用unsafe.freeMemory(address)方法释放native空间。
DirectByteBuffer继承MappedByteBuffer类,通过查看MappedByteBuffer类的注释:A direct byte buffer whose content is a memory-mapped region of a file.说明该区域就是内存映射文件区域。内存映射文件在windows 系统与linux系统中都有使用,与虚拟内存有些类似,虚拟内存是指当主存(内存)容量不够使用一部分外存(磁盘)充当主存,内存映射文件使用内存虚拟空间地址与磁盘文件建立一种映射关系,使得应用程序直接访问内存映射文件与同访问真实的磁盘文件一样操作,在正常模式下,应用程序对磁盘文件的访问通常需要经过一下步骤:应用程序空间->内核空间->磁盘文件,那么使用内存映射文件访问流程:应用程序->磁盘文件,内存映射文件持有磁盘地址,在访问时通过
地址映射转换直接访问磁盘空间,不需要经过内核空间到用户空间的传输,需要理解的内存映射文件对于应用程序或者操作系统都是透明的,二者
均可访问。应用场景包括:大文件之间的传输、zero copy.
大文件传输: 按照常理文件传输流程: 磁盘-> 内核空间->用户空间->内核空间->磁盘,中间进行多次数据的拷贝,使用内存文件映射方式传输,两个进程都可访问内存映射文件,使得在文件传输变为内存映射文件的传输,接受线程只需要获取目标内存映射地址读取然后写入磁盘即可。使用方式java.nio.channels.FileChannel.transferTo方法,FileChannel表示文件的通道,类似与输入输出流,输入输出流只能是单向通道,但是FileChannel 可直接转换输入输出,FileChannel可直接操作ByteBuffer,对ByteBuffer读或者写。接下来使用FileChannel 与 FileInputStream FileoutputStream 测试二者的传输效率:
结果:
zero copy: 零拷贝,对kafka 消费者性能提升的一个很重要的因素,普通模式下:磁盘->内核空间->用户空间->网卡,需要进行多次数据的传输通过使用,使用zero copy 可直接:磁盘->内核空间->网卡,省去了内核空间->用户空间,用户空间->网卡的步骤,传输速度更快。zero copy同样也是通过java.nio.channels.FileChannel.transferTo 将源数据直接通过内存空间文件映射方式发送到目标通道,此时目标通道就是网卡通道(SocketChannel)。