缓冲IO
通常应用程序访问硬盘数据或接收网络数据,数据会先被操作系统缓冲到内核缓冲区,再由应用程序copy到本地内存(用户空间),对于java来说就是JVM进程所占用的内存空间。
内存映射
相比于前面的缓冲IO,内存映射不再将数据copy到本地内存,而是在本地内存使用逻辑地址映射到内核空间,实际读写的是内核缓冲区,这样就减少了由内核缓冲区到应用程序本地内存的数据copy。
transferTo
public static void copyFileByChannel(File in, File out) { FileChannel src = new FileInputStream(in).getChannel(); FileChannel tgt = new FileOutputStream(out).getChannel(); for (long count = src.size(); count > 0; ) { long transferred = src.transferTo(src.position(), count, tgt); src.position(src.position() + transferred); count -= transferred; }}上面是一个使用FileChannel的简单示例,可以看到通过FileChannel的transferTo方法可以直接将输入管道传输至输出的管道。正常的读写,我们需要先将输入数据读取到本地内存,再将本地内存输出到目标文件,显然使用transferTo在用户空间减少了copy。
DirectByteBuffer
ByteBuffer byteBuf = ByteBuffer.allocateDirect(1024);
见名知义,DirecByteBuffer是将在堆外内存上进行分配、使用、回收。直接内存可以减少本地内存copy,对于IO密集型程序是可以带来性能优势的。同时由于直接内存在堆外,所以创建和销毁相比堆上Buffer会有额外的开销。通常垃圾回收不会主动回收这部分内存,所以保险起见应该主动去释放这部分内存空间,netty就是通过引用计数来回收直接内存的。
netty零拷贝
netty零拷贝参见上述Java的两种方式,毕竟netty是一个高性能Java框架。除了上述方法,netty还有许多工具组件,减少了我们在本地内存数据copy,比如CompositeByteBuf可以通过组合内存的方式把内存数据包组合到一起,比如ByteBuf的slice方法,可以创建一个共享对象而不改变原对象指针等。
总结
对于硬盘的输入输出零拷贝同样适用于网络数据的收发;对于Java而言零拷贝就是在用户空间减少了多余的数据拷贝。