java 的zero copy多在網絡應用程序中使用。Java的libaries在linux和unix中支持zero copy,關鍵的api是java.nio.channel.FileChannel的transferTo(),transferFrom()方法。咱們能夠用這兩個方法來把bytes直接從調用它的channel傳輸到另外一個writable byte channel,中間不會使data通過應用程序,以便提升數據轉移的效率。 java
許多web應用都會向用戶提供大量的靜態內容,這意味着有不少data從硬盤讀出以後,會原封不動的經過socket傳輸給用戶。這種操做看起來可能不會怎麼消耗CPU,可是實際上它是低效的:kernal把數據從disk讀出來,而後把它傳輸給user級的application,而後application再次把一樣的內容再傳回給處於kernal級的socket。這種場景下,application實際上只是做爲一種低效的中間介質,用來把disk file的data傳給socket。 linux
data每次穿過user-kernel boundary,都會被copy,這會消耗cpu,而且佔用RAM的帶寬。幸運的是,你能夠用一種叫作Zero-Copy的技術來去掉這些無謂的 copy。應用程序用zero copy來請求kernel直接把disk的data傳輸給socket,而不是經過應用程序傳輸。Zero copy大大提升了應用程序的性能,而且減小了kernel和user模式的上下文切換 web
使用kernel buffer作中介(而不是直接把data傳到user buffer中)看起來比較低效(多了一次copy)。然而實際上kernel buffer是用來提升性能的。在進行讀操做的時候,kernel buffer起到了預讀cache的做用。當寫請求的data size比kernel buffer的size小的時候,這可以顯著的提高性能。在進行寫操做時,kernel buffer的存在可使得寫請求徹底異步。 centos
悲劇的是,當請求的data size遠大於kernel buffer size的時候,這個方法自己變成了性能的瓶頸。由於data須要在disk,kernel buffer,user buffer之間拷貝不少次(每次寫滿整個buffer)。 api
而Zero copy正是經過消除這些多餘的data copy來提高性能。 網絡
經過網絡把一個文件傳輸給另外一個程序,在OS的內部,這個copy操做要經歷四次user mode和kernel mode之間的上下文切換,甚至連數據都被拷貝了四次,以下圖: app
具體步驟以下: dom
在linux 2.4及以上版本的內核中(如linux 6或centos 6以上的版本),開發者修改了socket buffer descriptor,使網卡支持 gather operation,經過kernel進一步減小數據的拷貝操做。這個方法不只減小了context switch,還消除了和CPU有關的數據拷貝。user層面的使用方法沒有變,可是內部原理卻發生了變化: 異步
展現經過網絡把一個文件從client傳到server的過程 socket
package zerocopy; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; public class TransferToServer { ServerSocketChannel listener = null; protected void mySetup() { InetSocketAddress listenAddr = new InetSocketAddress(9026); try { listener = ServerSocketChannel.open(); ServerSocket ss = listener.socket(); ss.setReuseAddress(true); ss.bind(listenAddr); System.out.println("監聽的端口:" + listenAddr.toString()); } catch (IOException e) { System.out.println("端口綁定失敗 : " + listenAddr.toString() + " 端口可能已經被使用,出錯緣由: " + e.getMessage()); e.printStackTrace(); } } public static void main(String[] args) { TransferToServer dns = new TransferToServer(); dns.mySetup(); dns.readData(); } private void readData() { ByteBuffer dst = ByteBuffer.allocate(4096); try { while (true) { SocketChannel conn = listener.accept(); System.out.println("建立的鏈接: " + conn); conn.configureBlocking(true); int nread = 0; while (nread != -1) { try { nread = conn.read(dst); } catch (IOException e) { e.printStackTrace(); nread = -1; } dst.rewind(); } } } catch (IOException e) { e.printStackTrace(); } } }
package zerocopy; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; public class TransferToClient { public static void main(String[] args) throws IOException { TransferToClient sfc = new TransferToClient(); sfc.testSendfile(); } public void testSendfile() throws IOException { String host = "localhost"; int port = 9026; SocketAddress sad = new InetSocketAddress(host, port); SocketChannel sc = SocketChannel.open(); sc.connect(sad); sc.configureBlocking(true); String fname = "src/main/java/zerocopy/test.data"; FileChannel fc = new FileInputStream(fname).getChannel(); long start = System.nanoTime(); long nsent = 0, curnset = 0; curnset = fc.transferTo(0, fc.size(), sc); System.out.println("發送的總字節數:" + curnset + " 耗時(ns):" + (System.nanoTime() - start)); try { sc.close(); fc.close(); } catch (IOException e) { System.out.println(e); } } }
其它zero copy的用法
package zerocopy; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; public class ZerocopyDemo { @SuppressWarnings("resource") public static void transferToDemo(String from, String to) throws IOException { FileChannel fromChannel = new RandomAccessFile(from, "rw").getChannel(); FileChannel toChannel = new RandomAccessFile(to, "rw").getChannel(); long position = 0; long count = fromChannel.size(); fromChannel.transferTo(position, count, toChannel); fromChannel.close(); toChannel.close(); } @SuppressWarnings("resource") public static void transferFromDemo(String from, String to) throws IOException { FileChannel fromChannel = new FileInputStream(from).getChannel(); FileChannel toChannel = new FileOutputStream(to).getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(fromChannel, position, count); fromChannel.close(); toChannel.close(); } public static void main(String[] args) throws IOException { String from="src/main/java/zerocopy/1.data"; String to="src/main/java/zerocopy/2.data"; // transferToDemo(from,to); transferFromDemo(from,to); } }
https://www.ibm.com/developerworks/linux/library/j-zerocopy/