Java sendFile的底层实现
前言
Java send file api是transferTo和transferFrom方法。
注: send file是磁盘到网卡的I/o优化。 相反,从网卡到磁盘都没有此IO优化。 也就是说,transferFrom方法没有这种福利。
本文稍微详细地介绍了Java是如何实现的。 请注意。 本文的代码版本是open JDK-8u 40-src-b25-10 _ feb _ 2015。
传输自分析
那么transferFrom的底层是什么呢? 简单来说,我只是使用了MMAP和堆外内存。
图像
在上面的transferFrom方法代码中,如下所示:
如果是普通文件通道,则使用mmap,否则使用类似于这次使用的SocketChannelImpl的堆外内存。
传输来自mmap详细信息:
图像
简单地说,在一个周期中,每次基于位置将源文件映射到一个mmap,最大为8M,然后顺序地将数据写入目标文件。
传输来自堆外内存详细信息:
图像
从上面的代码可以看出,
如果我们使用的是SocketChannelImpl,我们会在堆外的内存中行走,每次写入的周期最多可达8k。 用完回收,不是释放。
在此,取得堆外存储器方法:
图像
此缓冲区缓存为线程本地,如下图所示,是线程安全的。 类netty内存设计。
图像
请注意,此bufferCache是sun编写的简单版本的基于直接内存的Cache,是一个简单的内存池实现。 内部是数组,默认大小为16。 get方法的key是size。 也就是说,如果数组中的容量超过size,则返回此buffer。
16个源是JVM的基础实现,具体位置: IOUtil.c 140 line。
图像
假设此cache现在有16个插槽,内部现在有14个字节缓冲器。 此时count为14。
图像
start指针指向头部,执行get方法。 此时,从start开始扫描,找到了下标在I的要素。 因为他的capacity比给定的size大。 我们需要提取这个元素。
图像
此时,count需要减1为13,同时将头部的start要素移动到正好空闲的位置。
请注意,必须将start指针放在头部位置。
图像
运行start=(start1) % 16,下次再次获取时,还是从头开始。 你为什么要这么做? 为了避免无谓的遍历,遇到空要素时,直接返回即可。
具体请参照代码:
ByteBuffer bb=buffers[i];
if(bb==null )//避免无谓的循环
布雷克;
用完此ByteBuf后,必须返回————。 如果cache有空的话,是count 16。
怎么还?
有一个返回内存的util.releasetemporarydirectbuffer (bb )方法。 请注意,如果内存池已满,则调用free方法释放内存。
最终调用offerfirsttemporarydirectbuffer方法。
图像
如果添加到缓存池失败,请添加自由、缓存池逻辑:
图像
发行逻辑简单来说就是调用unsafe的自由内存方法
图像
仅限于篇幅,下次再说。 另外,还与一些虚引用的内容有关。
传输从方法总结
如果源类型为FileChannelImpl,请前往mmap,循环映射8MB并将其写入磁盘。
如果源类型为SocketChannelImpl,请在堆外内存中行走。 简单地说,循环放入堆外的存储器中,每8kb擦成磁盘。 注意:这个堆外内存被用来缓存忧郁的大米。 (堆外内存池化是一种常见的优化手段。)。 这种忧郁的大米是数组,长16,使用ThreadLocal提高性能。 每次检索时,如果目标数组小于忧郁的米字节缓冲器容量,则可以使用,如果已使用则可以,如果已满,则调用ui
传输分析
因为transferTo的方法很有趣,所以首先简单地说一下结论:
如果操作系统支持sendfile,请运行系统调用。
如果操作系统不支持,请去mmap。
如果mmap失败,走堆外的内存。
图像
代码如上所示。
注意:如果内核不能运行,则返回-2。 在jvm代码中,我们发现apple、linux、solaris和IBM的AIX都支持send file。
图像
上述代码位置: FileChannelImpl.c 156 line。
如何使用mmap写入网卡? 与transferFrom一样,每次映射最多8Mb的内存并将其打印到网卡上。 每次用完时clean。
私密状态语音地图{
cleanercl=(directbuffer ) bb ).cleaner );
if(cl!=null )
cl.clean (;
}
如何使用直接内存? 与transferFrom一样,每次最多使用8kb,在网卡中放置循环刷。 这里是补助金代码。
总结
查看并总结send file的Java级别实现。 只有transferTo用于send file,而且有条件。 具体如正文第二部分所示。
另一方面,transferFrom方法通常使用mmap或装载外部存储器。 我们似乎有自己能实现的事情,可能反而性能更好。 例如,可以使用更大的缓存而不是多次循环,也可以使用更大的mmap映射而不是8Mb。 每次都需要clean重新映射。