這是一個來自知乎上一個問題
- DirectBuffer 屬于堆外存房蝉,那應(yīng)該還是屬于用戶內(nèi)存,而不是內(nèi)核內(nèi)存竹宋?
- FileChannel 的read(ByteBuffer dst)函數(shù),write(ByteBuffer src)函數(shù)中敞贡,如果傳入的參數(shù)是HeapBuffer類型,則會臨時申請一塊DirectBuffer,進(jìn)行數(shù)據(jù)拷貝,而不是直接進(jìn)行數(shù)據(jù)傳輸哗总,這是出于什么原因?
Java NIO中的direct buffer(主要是DirectByteBuffer)其實(shí)是分兩部分的:
其中 DirectByteBuffer 自身是一個Java對象倍试,在Java堆中讯屈;而這個對象中有個long類型字段address,記錄著一塊調(diào)用 malloc() 申請到的native memory易猫。
問題1
DirectByteBuffer 自身是(Java)堆內(nèi)的耻煤,它背后真正承載數(shù)據(jù)的buffer是在(Java)堆外——native memory中的具壮。這是 malloc() 分配出來的內(nèi)存准颓,是用戶態(tài)的。
問題 2
//OpenJDK的 sun.nio.ch.IOUtil.write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd) 的實(shí)現(xiàn)
static int write(FileDescriptor fd, ByteBuffer src, long position,
NativeDispatcher nd)
throws IOException
{
if (src instanceof DirectBuffer)
return writeFromNativeBuffer(fd, src, position, nd);
// Substitute a native buffer
int pos = src.position();
int lim = src.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
try {
bb.put(src);
bb.flip();
// Do not update src until we see how many bytes were written
src.position(pos);
int n = writeFromNativeBuffer(fd, bb, position, nd);
if (n > 0) {
// now update src
src.position(pos + n);
}
return n;
} finally {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
這里其實(shí)是在遷就OpenJDK里的HotSpot VM的一點(diǎn)實(shí)現(xiàn)細(xì)節(jié)棺妓。HotSpot VM里的GC除了CMS之外都是要移動對象的攘已,是所謂“compacting GC”。如果要把一個Java里的 byte[] 對象的引用傳給native代碼怜跑,讓native代碼直接訪問數(shù)組的內(nèi)容的話样勃,就必須要保證native代碼在訪問的時候這個 byte[] 對象不能被移動吠勘,也就是要被“pin”(釘)住∠靠簦可惜HotSpot VM出于一些取舍而決定不實(shí)現(xiàn)單個對象層面的object pinning剧防,要pin的話就得暫時禁用GC——也就等于把整個Java堆都給pin住。HotSpot VM對JNI的Critical系A(chǔ)PI就是這樣實(shí)現(xiàn)的辫樱。這用起來就不那么順手峭拘。所以 Oracle/Sun JDK / OpenJDK 的這個地方就用了點(diǎn)繞彎的做法。它假設(shè)把 HeapByteBuffer 背后的 byte[] 里的內(nèi)容拷貝一次是一個時間開銷可以接受的操作狮暑,同時假設(shè)真正的I/O可能是一個很慢的操作鸡挠。于是它就先把 HeapByteBuffer 背后的 byte[] 的內(nèi)容拷貝到一個 DirectByteBuffer 背后的native memory去,這個拷貝會涉及 sun.misc.Unsafe.copyMemory() 的調(diào)用搬男,背后是類似 memcpy() 的實(shí)現(xiàn)拣展。這個操作本質(zhì)上是會在整個拷貝過程中暫時不允許發(fā)生GC的,雖然實(shí)現(xiàn)方式跟JNI的Critical系A(chǔ)PI不太一樣缔逛。(具體來說是 Unsafe.copyMemory() 是HotSpot VM的一個intrinsic方法备埃,中間沒有safepoint所以GC無法發(fā)生)。然后數(shù)據(jù)被拷貝到native memory之后就好辦了译株,就去做真正的I/O瓜喇,把 DirectByteBuffer 背后的native memory地址傳給真正做I/O的函數(shù)。這邊就不需要再去訪問Java對象去讀寫要做I/O的數(shù)據(jù)了歉糜。