這篇文章主要介紹了java 中Buffer源碼的分析的相關資料,需要的朋友可以參考下
java 中Buffer源碼的分析
Buffer
Buffer的類別圖如下:
#除了Boolean,其他基本資料型別都有對應的Buffer,但是只有ByteBuffer才能和Channel互動。只有ByteBuffer才能產生Direct的buffer,其他資料類型的Buffer只能產生Heap類型的Buffer。 ByteBuffer可以產生其他資料類型的視圖Buffer,如果ByteBuffer本身是Direct的,則產生的各視圖Buffer也是Direct的。
Direct和Heap類型Buffer的本質
首選說說JVM是怎麼進行IO操作的。
JVM在需要透過作業系統呼叫完成IO操作,例如可以透過read系統呼叫完成檔案的讀取。 read的原型是:ssize_t read(int fd,void *buf,size_t nbytes),和其他的IO系統呼叫類似,一般需要緩衝區作為其中一個參數,該緩衝區要求是連續的。
Buffer分為Direct和Heap兩類,以下分別說明這兩類buffer。
Heap
Heap類型的Buffer存在於JVM的堆上,這部分記憶體的回收與整理和普通的物件一樣。 Heap類型的Buffer物件都包含一個對應基本資料型別的陣列屬性(例如:final **[] hb),陣列才是Heap類型Buffer的底層緩衝區。
但是Heap類型的Buffer不能直接作為緩衝區參數進行系統調用,主要因為下面兩個原因。
JVM在GC時可能會移動緩衝區(複製-整理),緩衝區的位址不固定。
系統呼叫時,緩衝區需要是連續的,但陣列可能不是連續的(JVM的實作沒要求連續)。
所以使用Heap類型的Buffer進行IO時,JVM需要產生一個臨時Direct類型的Buffer,然後進行資料複製,再使用臨時Direct的
#Buffer作為參數進行作業系統呼叫。這造成很低的效率,主要是因為兩個原因:
需要把資料從Heap型別的Buffer裡面複製到暫時建立的Direct的Buffer裡面。
可能產生大量的Buffer對象,進而提高GC的頻率。所以在IO操作時,可以重複利用Buffer來優化。
Direct
Direct類型的buffer,不存在於堆上,而是JVM透過malloc直接分配的一段連續的內存,這部分內存成為直接內存,JVM進行IO系統調用時使用的是直接內存作為緩衝區。
-XX:MaxDirectMemorySize,透過這個配置可以設定允許分配的最大直接記憶體的大小(MappedByteBuffer分配的記憶體不受此組態影響)。
直接記憶體的回收和堆疊記憶體的回收不同,如果直接記憶體使用不當,很容易造成OutOfMemoryError。 Java沒有提供顯示的方法去主動釋放直接內存,sun.misc.Unsafe類別可以進行直接的底層內存操作,透過該類別可以主動釋放和管理直接內存。同理,也應該重複利用直接記憶體以提高效率。
MappedByteBuffer與DirectByteBuffer之間的關係
This is a little bit backwards: By rights MappedByteBuffer should be a subclass of DirectByteBuffer, but to keep , and for optimization purposes, it's easier to do it the other way around.This works because DirectByteBuffer is a package-private class.(本段話摘自MappedByteBuffer的源碼)##Mapp#Mapp映射buffer(自己看看虛擬記憶體),但是DirectByteBuffer只是說明該部分記憶體是JVM在直接記憶體區分配的連續緩衝區,並不一是映射的。也就是說MappedByteBuffer應該是DirectByteBuffer的子類,但是為了方便和最佳化,把MappedByteBuffer當作了DirectByteBuffer的父類。另外,雖然MappedByteBuffer在邏輯上應該是DirectByteBuffer的子類,而且MappedByteBuffer的記憶體的GC和直接記憶體的GC類似(和堆GC不同),但是分配的MappedByteBuffer的大小不受-XX:MaxDirectMemorySize參數影響。
MappedByteBuffer封裝的是記憶體對映檔案操作,也就是只能進行檔案IO操作。 MappedByteBuffer是根據mmap產生的映射緩衝區,這部分緩衝區被映射到對應的文件頁上,屬於直接內存在用戶態,透過MappedByteBuffer可以直接操作映射緩衝區,而這部分緩衝區又被映射到文件頁上,作業系統透過對應記憶體頁的調入和調出完成檔案的寫入和寫出。
MappedByteBuffer
#
透過FileChannel.map(MapMode mode,long position, long size)得到MappedByteBuffer,下面結合原始碼說明MappedByteBuffer的產生過程。
FileChannel.map的原始碼:
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { ensureOpen(); if (position < 0L) throw new IllegalArgumentException("Negative position"); if (size < 0L) throw new IllegalArgumentException("Negative size"); if (position + size < 0) throw new IllegalArgumentException("Position + size overflow"); //最大2G if (size > Integer.MAX_VALUE) throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE"); int imode = -1; if (mode == MapMode.READ_ONLY) imode = MAP_RO; else if (mode == MapMode.READ_WRITE) imode = MAP_RW; else if (mode == MapMode.PRIVATE) imode = MAP_PV; assert (imode >= 0); if ((mode != MapMode.READ_ONLY) && !writable) throw new NonWritableChannelException(); if (!readable) throw new NonReadableChannelException(); long addr = -1; int ti = -1; try { begin(); ti = threads.add(); if (!isOpen()) return null; //size()返回实际的文件大小 //如果实际文件大小不符合,则增大文件的大小,文件的大小被改变,文件增大的部分默认设置为0。 if (size() < position + size) { // Extend file size if (!writable) { throw new IOException("Channel not open for writing " + "- cannot extend file to required size"); } int rv; do { //增大文件的大小 rv = nd.truncate(fd, position + size); } while ((rv == IOStatus.INTERRUPTED) && isOpen()); } //如果要求映射的文件大小为0,则不调用操作系统的mmap调用,只是生成一个空间容量为0的DirectByteBuffer //并返回 if (size == 0) { addr = 0; // a valid file descriptor is not required FileDescriptor dummy = new FileDescriptor(); if ((!writable) || (imode == MAP_RO)) return Util.newMappedByteBufferR(0, 0, dummy, null); else return Util.newMappedByteBuffer(0, 0, dummy, null); } //allocationGranularity的大小在我的系统上是4K //页对齐,pagePosition为第多少页 int pagePosition = (int)(position % allocationGranularity); //从页的最开始映射 long mapPosition = position - pagePosition; //因为从页的最开始映射,增大映射空间 long mapSize = size + pagePosition; try { // If no exception was thrown from map0, the address is valid //native方法,源代码在openjdk/jdk/src/solaris/native/sun/nio/ch/FileChannelImpl.c, //参见下面的说明 addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError x) { // An OutOfMemoryError may indicate that we've exhausted memory // so force gc and re-attempt map System.gc(); try { Thread.sleep(100); } catch (InterruptedException y) { Thread.currentThread().interrupt(); } try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError y) { // After a second OOME, fail throw new IOException("Map failed", y); } } // On Windows, and potentially other platforms, we need an open // file descriptor for some mapping operations. FileDescriptor mfd; try { mfd = nd.duplicateForMapping(fd); } catch (IOException ioe) { unmap0(addr, mapSize); throw ioe; } assert (IOStatus.checkAll(addr)); assert (addr % allocationGranularity == 0); int isize = (int)size; Unmapper um = new Unmapper(addr, mapSize, isize, mfd); if ((!writable) || (imode == MAP_RO)) { return Util.newMappedByteBufferR(isize, addr + pagePosition, mfd, um); } else { return Util.newMappedByteBuffer(isize, addr + pagePosition, mfd, um); } } finally { threads.remove(ti); end(IOStatus.checkAll(addr)); } }
map0的原始碼實作:
JNIEXPORT jlong JNICALL Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len) { void *mapAddress = 0; jobject fdo = (*env)->GetObjectField(env, this, chan_fd); //linux系统调用是通过整型的文件id引用文件的,这里得到文件id jint fd = fdval(env, fdo); int protections = 0; int flags = 0; if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ; flags = MAP_SHARED; } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ; flags = MAP_SHARED; } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections = PROT_WRITE | PROT_READ; flags = MAP_PRIVATE; } //这里就是操作系统调用了,mmap64是宏定义,实际最后调用的是mmap mapAddress = mmap64( 0, /* Let OS decide location */ len, /* Number of bytes to map */ protections, /* File permissions */ flags, /* Changes are shared */ fd, /* File descriptor of mapped file */ off); /* Offset into file */ if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { //如果没有映射成功,直接抛出OutOfMemoryError JNU_ThrowOutOfMemoryError(env, "Map failed"); return IOS_THROWN; } return handle(env, -1, "Map failed"); } return ((jlong) (unsigned long) mapAddress); }
雖然FileChannel.map()的zise參數是long,但是size的大小最大為Integer.MAX_VALUE,也就是最大隻能映射最大2G大小的空間。實際上作業系統提供的MMAP可以分配更大的空間,但是JAVA限制在2G,ByteBuffer等Buffer也最大隻能分配2G大小的緩衝區。
MappedByteBuffer是透過mmap產生得到的緩衝區,這部分緩衝區是由作業系統直接建立和管理的,最後JVM透過unmmap讓作業系統直接釋放這部分記憶體。
Haep****Buffer
下面以ByteBuffer為例,說明Heap型別Buffer的細節。
此類型的Buffer可以透過下面方式產生:
ByteBuffer.allocate(int capacity)
ByteBuffer.wrap(byte[] array) 使用傳入的陣列作為底層緩衝區,變更陣列會影響緩衝區,變更緩衝區也會影響陣列。
ByteBuffer.wrap(byte[] array,int offset, int length)
使用傳入的陣列的一部分作為底層緩衝區,變更數組的對應部分會影響緩衝區,變更緩衝區也會影響數組。
DirectByteBuffer
DirectByteBuffer只能透過ByteBuffer.allocateDirect(int capacity) 產生。
ByteBuffer.allocateDirect()原始碼如下:
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
DirectByteBuffer()原始碼如下:
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); //直接内存是否要页对齐,我本机测试的不用 boolean pa = VM.isDirectMemoryPageAligned(); //页的大小,本机测试的是4K int ps = Bits.pageSize(); //如果页对齐,则size的大小是ps+cap,ps是一页,cap也是从新的一页开始,也就是页对齐了 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); //JVM维护所有直接内存的大小,如果已分配的直接内存加上本次要分配的大小超过允许分配的直接内存的最大值会 //引起GC,否则允许分配并把已分配的直接内存总量加上本次分配的大小。如果GC之后,还是超过所允许的最大值, //则throw new OutOfMemoryError("Direct buffer memory"); Bits.reserveMemory(size, cap); long base = 0; try { //是吧,unsafe可以直接操作底层内存 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) {、 //没有分配成功,把刚刚加上的已分配的直接内存的大小减去。 Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
unsafe.allocateMemory()的原始碼在openjdk/src/openjdk/hotspot/src/share/vm/prims/unsafe.cpp中。具體的源碼如下:
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size)) UnsafeWrapper("Unsafe_AllocateMemory"); size_t sz = (size_t)size; if (sz != (julong)size || size < 0) { THROW_0(vmSymbols::java_lang_IllegalArgumentException()); } if (sz == 0) { return 0; } sz = round_to(sz, HeapWordSize); //最后调用的是 u_char* ptr = (u_char*)::malloc(size + space_before + space_after),也就是malloc。 void* x = os::malloc(sz, mtInternal); if (x == NULL) { THROW_0(vmSymbols::java_lang_OutOfMemoryError()); } //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize); return addr_to_java(x); UNSAFE_END
JVM透過malloc分配得到連續的緩衝區,這部分緩衝區可以直接作為緩衝區參數進行作業系統呼叫。
以上是解析java 中的Buffer源碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!