詳細總結Java堆棧內存、堆外內存、零拷貝淺析與代碼實現
一、堆棧內存
堆棧內存,顧名思義,指的是堆內存以及棧內存,其中,堆內存是由Java GC進行管理的內存區域,而棧內存則是線程內存。關於棧內存,這裡不去細說。以Hotspot為例,堆內存的簡要結構如下圖所示:
而堆棧的關系,我們可以通過一行簡單的代碼來理解:
public static void main(String[] args) { Object o = new Object(); }
上述代碼主要完成瞭兩件事,new Object( ) 在堆上開辟瞭一塊內存,也就是說,new Object( )是分配在堆上的;而變量o,則是在線程main的棧上面的,它指向瞭new Object( ) 開辟的堆內存地址。簡單來說,程序中創建的對象,都存儲在堆內存中,棧內存包含對它的引用。
二、堆外內存
簡單來說,除瞭堆棧內存,剩下的就都是堆外內存瞭(當然,這是從Java運行時內存的角度來看),堆外內存直接受操作系統管理,而不是虛擬機。而使用堆外內存的原因,主要有幾點:
- 一定程度上減少瞭GC,堆外內存是直接受操作系統管理的,而不是JVM,因此使用堆外內存的話,就可以保持一個比較小的堆內內存,減少垃圾回收對程序性能的影響。這一塊,在Kafka中就應用得很好,感興趣的同學可以去瞭解一下;
- 還有一個更大的優點,就是提高IO操作的效率!這裡就涉及用戶態與內核態,以及內核緩沖區的概念,具體可以看筆者之前的一篇文章Java隨筆記 – 內核緩沖區與進程緩沖區。其中,堆內內存其實就是用戶進程的進程緩沖區,屬於用戶態,而堆外內存由操作系統管理,屬於內核態。如果從堆內向磁盤寫數據,數據會被先復制到堆外內存,即內核緩沖區,然後再由OS寫入磁盤,但使用堆外內存的話則可以避免這個復制操作。
三、零拷貝
總結上述內容中對堆棧內存與堆外內存的說明,主要解決瞭兩個疑問:“零拷貝”是從哪拷貝到哪?“零拷貝”是怎麼優化掉這一拷貝操作的?
- 用戶進程需要像磁盤寫數據時,需要將用戶緩沖區(堆內內存)中的內容拷貝到內核緩沖區(堆外內存)中,操作系統再將內核緩沖區中的內容寫進磁盤中;
- 通過在用戶進程中,直接申請堆外內存,存儲其需要寫進磁盤的數據,就能夠省掉上述拷貝操作。
在Java中,提供瞭一些使用堆外內存以及DMA的方法,能夠在很大程度上優化用戶進程的IO效率。這裡,給出一份拷貝文件的代碼,分別使用BIO、NIO和使用堆外內存的NIO進行文件復制,簡單對比其耗時。
這裡我使用一個200MB左右的pdf文件進行拷貝操作,你可以另外指定更大的文件,文件越大對比越明顯。這裡我運行出來的延時,BIO的平均耗時1500ms上下,NIO耗時120ms左右, 使用堆外內存的NIO耗時100ms上下。
package top.jiangnanmax.nio; import java.io.*; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * @author jiangnanmax * @email [email protected] * @description CopyCompare * @date 2021/5/7 **/ public class CopyCompare { public static void main(String[] args) throws Exception { String inputFile = "/tmp/nio/input/HyperLedger.pdf"; String outputFile = "/tmp/nio/output/HyperLedger.pdf"; long start = System.currentTimeMillis(); nioCopyByDirectMem(inputFile, outputFile); long end = System.currentTimeMillis(); System.out.println("cost time: " + (end - start) + " ms"); deleteFile(outputFile); } /** * 使用傳統IO進行文件復制 * * 平均耗時 15** ms * * @param sourcePath * @param destPath */ private static void bioCopy(String sourcePath, String destPath) throws Exception { File sourceFile = new File(sourcePath); File destFile = new File(destPath); if (!destFile.exists()) { destFile.createNewFile(); } FileInputStream inputStream = new FileInputStream(sourceFile); FileOutputStream outputStream = new FileOutputStream(destFile); byte[] buffer = new byte[512]; int lenRead; while ((lenRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, lenRead); } inputStream.close(); outputStream.close(); } /** * 使用NIO進行文件復制,但不使用堆外內存 * * 平均耗時 1** ms, 比BIO直接快瞭一個數量級??? * * @param sourcePath * @param destPath */ private static void nioCopy(String sourcePath, String destPath) throws Exception { File sourceFile = new File(sourcePath); File destFile = new File(destPath); if (!destFile.exists()) { destFile.createNewFile(); } FileInputStream inputStream = new FileInputStream(sourceFile); FileOutputStream outputStream = new FileOutputStream(destFile); FileChannel inputChannel = inputStream.getChannel(); FileChannel outputChannel = outputStream.getChannel(); // transferFrom底層調用的應該是sendfile // 直接在兩個文件描述符之間進行瞭數據傳輸 // DMA outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); inputChannel.close(); outputChannel.close(); inputStream.close(); outputStream.close(); } /** * 使用NIO進行文件復制,並使用堆外內存 * * 平均耗時100ms上下,比沒使用堆外內存的NIO快一點 * * @param sourcePath * @param destPath */ private static void nioCopyByDirectMem(String sourcePath, String destPath) throws Exception { File sourceFile = new File(sourcePath); File destFile = new File(destPath); if (!destFile.exists()) { destFile.createNewFile(); } FileInputStream inputStream = new FileInputStream(sourceFile); FileOutputStream outputStream = new FileOutputStream(destFile); FileChannel inputChannel = inputStream.getChannel(); FileChannel outputChannel = outputStream.getChannel(); MappedByteBuffer buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size()); outputChannel.write(buffer); inputChannel.close(); outputChannel.close(); inputStream.close(); outputStream.close(); } /** * 刪除目標文件 * * @param target */ private static void deleteFile(String target) { File file = new File(target); file.delete(); } }
到此這篇關於詳細總結Java堆棧內存、堆外內存、零拷貝淺析與代碼實現的文章就介紹到這瞭,更多相關Java堆棧內存 堆外內存 零拷貝淺析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 詳解NIO中FileChannel文件流的簡單使用
- Java FileInputStream與FileOutputStream使用詳解
- Java IO之字節輸入輸出流詳解
- Java實現文件壓縮為zip和解壓zip壓縮包
- Java深入淺出說流的使用