Java 的類實(shí)例一般在 JVM 堆上分配,而 Java 是通過 JNI 調(diào)用 C 代碼來實(shí)現(xiàn) Socket 通信的仰美,那么 C 代碼在運(yùn)行過程中需要的內(nèi)存又是從哪里分配的呢?C 代碼能否直接操作 Java 堆停士?
為了回答這些問題舅柜,我先來說說 JVM 和用戶進(jìn)程的關(guān)系。如果你想運(yùn)行一個(gè) Java 類文件但骨,可以用下面的 Java 命令來執(zhí)行
java my.class
這個(gè)命令行中的java其實(shí)是一個(gè)可執(zhí)行程序励七,這個(gè)程序會(huì)創(chuàng)建 JVM 來加載和運(yùn)行你的 Java 類。
操作系統(tǒng)會(huì)創(chuàng)建一個(gè)進(jìn)程來執(zhí)行這個(gè)java可執(zhí)行程序奔缠,而每個(gè)進(jìn)程都有自己的虛擬地址空間掠抬,JVM 用到的內(nèi)存(包括堆、棧和方法區(qū))就是從進(jìn)程的虛擬地址空間上分配的校哎。請(qǐng)你注意的是两波,JVM 內(nèi)存只是進(jìn)程空間的一部分,除此之外進(jìn)程空間內(nèi)還有代碼段闷哆、數(shù)據(jù)段腰奋、內(nèi)存映射區(qū)、內(nèi)核空間等阳准。JVM 的角度看氛堕,JVM 內(nèi)存之外的部分叫作本地內(nèi)存,C 程序代碼在運(yùn)行過程中用到的內(nèi)存就是本地內(nèi)存中分配的野蝇。下面我們通過一張圖來理解一下讼稚。
那 HeapByteBuffer 和 DirectByteBuffer 有什么區(qū)別呢?HeapByteBuffer 對(duì)象本身在 JVM 堆上分配绕沈,并且它持有的字節(jié)數(shù)組byte[]也是在 JVM 堆上分配锐想。
但是如果用HeapByteBuffer來接收網(wǎng)絡(luò)數(shù)據(jù),需要把數(shù)據(jù)從內(nèi)核先拷貝到一個(gè)臨時(shí)的本地內(nèi)存乍狐,再從臨時(shí)本地內(nèi)存拷貝到 JVM 堆,而不是直接從內(nèi)核拷貝到 JVM 堆上赠摇。這是為什么呢?這是因?yàn)閿?shù)據(jù)從內(nèi)核拷貝到 JVM 堆的過程中,JVM 可能會(huì)發(fā)生 GC藕帜,GC 過程中對(duì)象可能會(huì)被移動(dòng)烫罩,也就是說 JVM 堆上的字節(jié)數(shù)組可能會(huì)被移動(dòng),這樣的話 Buffer 地址就失效了洽故。如果這中間經(jīng)過本地內(nèi)存中轉(zhuǎn)贝攒,從本地內(nèi)存到 JVM 堆的拷貝過程中 JVM 可以保證不做 GC。
如果使用 HeapByteBuffer时甚,你會(huì)發(fā)現(xiàn) JVM 堆和內(nèi)核之間多了一層中轉(zhuǎn)隘弊,而 DirectByteBuffer 用來解決這個(gè)問題,DirectByteBuffer 對(duì)象本身在 JVM 堆上,但是它持有的字節(jié)數(shù)組不是從 JVM 堆上分配的荒适,而是從本地內(nèi)存分配的梨熙。
DirectByteBuffer 對(duì)象中有個(gè) long 類型字段 address,記錄著本地內(nèi)存的地址刀诬,這樣在接收數(shù)據(jù)的時(shí)候咽扇,直接把這個(gè)本地內(nèi)存地址傳遞給 C 程序,C 程序會(huì)將網(wǎng)絡(luò)數(shù)據(jù)從內(nèi)核拷貝到這個(gè)本地內(nèi)存舅列,JVM 可以直接讀取這個(gè)本地內(nèi)存肌割,這種方式比 HeapByteBuffer 少了一次拷貝卧蜓,因此一般來說它的速度會(huì)比 HeapByteBuffer 快好幾倍帐要。你可以通過上面的圖加深理解。
那為什么DirectByteBuffer的性能比HeapByteBuffer差依然在使用呢弥奸?
這是因?yàn)楸镜貎?nèi)存不好管理榨惠,發(fā)生內(nèi)存泄漏難以定位,從穩(wěn)定性考慮盛霎,
HeapByteBuffer更好赠橙。