詳細(xì)總結(jié)Java堆棧內(nèi)存、堆外內(nèi)存、零拷貝淺析與代碼實(shí)現(xiàn)
一、堆棧內(nèi)存
堆棧內(nèi)存,顧名思義,指的是堆內(nèi)存以及棧內(nèi)存,其中,堆內(nèi)存是由Java GC進(jìn)行管理的內(nèi)存區(qū)域,而棧內(nèi)存則是線(xiàn)程內(nèi)存。關(guān)于棧內(nèi)存,這里不去細(xì)說(shuō)。以Hotspot為例,堆內(nèi)存的簡(jiǎn)要結(jié)構(gòu)如下圖所示:

而堆棧的關(guān)系,我們可以通過(guò)一行簡(jiǎn)單的代碼來(lái)理解:
public static void main(String[] args) {
Object o = new Object();
}
上述代碼主要完成了兩件事,new Object( ) 在堆上開(kāi)辟了一塊內(nèi)存,也就是說(shuō),new Object( )是分配在堆上的;而變量o,則是在線(xiàn)程main的棧上面的,它指向了new Object( ) 開(kāi)辟的堆內(nèi)存地址。簡(jiǎn)單來(lái)說(shuō),程序中創(chuàng)建的對(duì)象,都存儲(chǔ)在堆內(nèi)存中,棧內(nèi)存包含對(duì)它的引用。
二、堆外內(nèi)存
簡(jiǎn)單來(lái)說(shuō),除了堆棧內(nèi)存,剩下的就都是堆外內(nèi)存了(當(dāng)然,這是從Java運(yùn)行時(shí)內(nèi)存的角度來(lái)看),堆外內(nèi)存直接受操作系統(tǒng)管理,而不是虛擬機(jī)。而使用堆外內(nèi)存的原因,主要有幾點(diǎn):
- 一定程度上減少了GC,堆外內(nèi)存是直接受操作系統(tǒng)管理的,而不是JVM,因此使用堆外內(nèi)存的話(huà),就可以保持一個(gè)比較小的堆內(nèi)內(nèi)存,減少垃圾回收對(duì)程序性能的影響。這一塊,在Kafka中就應(yīng)用得很好,感興趣的同學(xué)可以去了解一下;
- 還有一個(gè)更大的優(yōu)點(diǎn),就是提高IO操作的效率!這里就涉及用戶(hù)態(tài)與內(nèi)核態(tài),以及內(nèi)核緩沖區(qū)的概念,具體可以看筆者之前的一篇文章Java隨筆記 - 內(nèi)核緩沖區(qū)與進(jìn)程緩沖區(qū)。其中,堆內(nèi)內(nèi)存其實(shí)就是用戶(hù)進(jìn)程的進(jìn)程緩沖區(qū),屬于用戶(hù)態(tài),而堆外內(nèi)存由操作系統(tǒng)管理,屬于內(nèi)核態(tài)。如果從堆內(nèi)向磁盤(pán)寫(xiě)數(shù)據(jù),數(shù)據(jù)會(huì)被先復(fù)制到堆外內(nèi)存,即內(nèi)核緩沖區(qū),然后再由OS寫(xiě)入磁盤(pán),但使用堆外內(nèi)存的話(huà)則可以避免這個(gè)復(fù)制操作。

三、零拷貝
總結(jié)上述內(nèi)容中對(duì)堆棧內(nèi)存與堆外內(nèi)存的說(shuō)明,主要解決了兩個(gè)疑問(wèn):“零拷貝”是從哪拷貝到哪?“零拷貝”是怎么優(yōu)化掉這一拷貝操作的?
- 用戶(hù)進(jìn)程需要像磁盤(pán)寫(xiě)數(shù)據(jù)時(shí),需要將用戶(hù)緩沖區(qū)(堆內(nèi)內(nèi)存)中的內(nèi)容拷貝到內(nèi)核緩沖區(qū)(堆外內(nèi)存)中,操作系統(tǒng)再將內(nèi)核緩沖區(qū)中的內(nèi)容寫(xiě)進(jìn)磁盤(pán)中;
- 通過(guò)在用戶(hù)進(jìn)程中,直接申請(qǐng)堆外內(nèi)存,存儲(chǔ)其需要寫(xiě)進(jìn)磁盤(pán)的數(shù)據(jù),就能夠省掉上述拷貝操作。
在Java中,提供了一些使用堆外內(nèi)存以及DMA的方法,能夠在很大程度上優(yōu)化用戶(hù)進(jìn)程的IO效率。這里,給出一份拷貝文件的代碼,分別使用BIO、NIO和使用堆外內(nèi)存的NIO進(jìn)行文件復(fù)制,簡(jiǎn)單對(duì)比其耗時(shí)。
這里我使用一個(gè)200MB左右的pdf文件進(jìn)行拷貝操作,你可以另外指定更大的文件,文件越大對(duì)比越明顯。這里我運(yùn)行出來(lái)的延時(shí),BIO的平均耗時(shí)1500ms上下,NIO耗時(shí)120ms左右, 使用堆外內(nèi)存的NIO耗時(shí)100ms上下。
package top.jiangnanmax.nio;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author jiangnanmax
* @email jiangnanmax@gmail.com
* @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);
}
/**
* 使用傳統(tǒng)IO進(jìn)行文件復(fù)制
*
* 平均耗時(shí) 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進(jìn)行文件復(fù)制,但不使用堆外內(nèi)存
*
* 平均耗時(shí) 1** ms, 比BIO直接快了一個(gè)數(shù)量級(jí)???
*
* @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底層調(diào)用的應(yīng)該是sendfile
// 直接在兩個(gè)文件描述符之間進(jìn)行了數(shù)據(jù)傳輸
// DMA
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
inputChannel.close();
outputChannel.close();
inputStream.close();
outputStream.close();
}
/**
* 使用NIO進(jìn)行文件復(fù)制,并使用堆外內(nèi)存
*
* 平均耗時(shí)100ms上下,比沒(méi)使用堆外內(nèi)存的NIO快一點(diǎn)
*
* @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();
}
/**
* 刪除目標(biāo)文件
*
* @param target
*/
private static void deleteFile(String target) {
File file = new File(target);
file.delete();
}
}
到此這篇關(guān)于詳細(xì)總結(jié)Java堆棧內(nèi)存、堆外內(nèi)存、零拷貝淺析與代碼實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Java堆棧內(nèi)存 堆外內(nèi)存 零拷貝淺析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)的日期時(shí)間轉(zhuǎn)換工具類(lèi)完整示例
這篇文章主要介紹了java實(shí)現(xiàn)的日期時(shí)間轉(zhuǎn)換工具類(lèi),結(jié)合完整實(shí)例形式分析了java針對(duì)日期時(shí)間常見(jiàn)的轉(zhuǎn)換、計(jì)算、格式化等相關(guān)操作與封裝技巧,需要的朋友可以參考下2019-10-10
Spring的Bean生命周期之BeanDefinition詳解
這篇文章主要介紹了Spring的Bean生命周期之BeanDefinition詳解,在spring bean創(chuàng)建過(guò)程 依賴(lài) BeanDefinition 中的信息處理bean的生產(chǎn),BeanDefinition 是 Spring Framework 中定義 Bean 的配置元信息接口,需要的朋友可以參考下2023-12-12
MyBatis-Plus 快速入門(mén)案例(小白教程)
這篇文章主要介紹了MyBatis-Plus 快速入門(mén)案例(小白教程),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
java調(diào)用shell命令并獲取執(zhí)行結(jié)果的示例
今天小編就為大家分享一篇java調(diào)用shell命令并獲取執(zhí)行結(jié)果的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Java多線(xiàn)程中的Exchanger應(yīng)用簡(jiǎn)析
這篇文章主要介紹了Java多線(xiàn)程中的Exchanger應(yīng)用簡(jiǎn)析,Exchanger提供了一個(gè)同步點(diǎn)exchange方法,兩個(gè)線(xiàn)程調(diào)用exchange方法時(shí),無(wú)論調(diào)用時(shí)間先后,兩個(gè)線(xiàn)程會(huì)互相等到線(xiàn)程到達(dá)exchange方法調(diào)用點(diǎn),此時(shí)兩個(gè)線(xiàn)程可以交換數(shù)據(jù),將本線(xiàn)程產(chǎn)出數(shù)據(jù)傳遞給對(duì)方,需要的朋友可以參考下2023-12-12
基于spring?@Cacheable?注解的spel表達(dá)式解析執(zhí)行邏輯
這篇文章主要介紹了spring?@Cacheable?注解的spel表達(dá)式解析執(zhí)行邏輯,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
java如何調(diào)用kettle設(shè)置變量和參數(shù)
文章簡(jiǎn)要介紹了如何在Java中調(diào)用Kettle,并重點(diǎn)討論了變量和參數(shù)的區(qū)別,以及在Java代碼中如何正確設(shè)置和使用這些變量,避免覆蓋Kettle中已設(shè)置的變量,作者分享了個(gè)人經(jīng)驗(yàn),并鼓勵(lì)大家參考和使用腳本之家2025-01-01
java使用wait和notify實(shí)現(xiàn)線(xiàn)程通信
這篇文章主要為大家詳細(xì)介紹了java如何使用wait和notify實(shí)現(xiàn)線(xiàn)程之間通信,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10
SpringBoot整合Kafka工具類(lèi)的詳細(xì)代碼
Kafka是一種高吞吐量的分布式發(fā)布訂閱消息系統(tǒng),它可以處理消費(fèi)者在網(wǎng)站中的所有動(dòng)作流數(shù)據(jù),這篇文章主要介紹了SpringBoot整合Kafka工具類(lèi)的代碼詳解,需要的朋友可以參考下2022-09-09

