Native Umgebung: Linux 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Das Klassendiagramm von Buffer lautet wie folgt:
In Zusätzlich zu Boolean verfügen andere grundlegende Datentypen über entsprechende Puffer, aber nur ByteBuffer kann mit Channel interagieren. Nur ByteBuffer kann Direktpuffer generieren Puffer anderer Datentypen können nur Puffer vom Heap-Typ generieren. ByteBuffer kann Ansichtspuffer anderer Datentypen generieren. Wenn ByteBuffer selbst direkt ist, , dann ist jeder generierte Ansichtspuffer auch direkt .
Die erste Wahl besteht darin, darüber zu sprechen, wie die JVM E/A-Vorgänge ausführt.
JVM muss E/A-Vorgänge über Betriebssystemaufrufe abschließen. Beispielsweise kann das Lesen von Dateien über Lesesystemaufrufe abgeschlossen werden. Der Leseprototyp lautet: ssize_t read(int fd,void *buf,size_t nbytes)
, ähnlich wie bei anderen E/A-Systemaufrufen, erfordert im Allgemeinen einen Puffer als einen der Parameter, und der Puffer muss kontinuierlich sein.
Puffer ist in zwei Kategorien unterteilt: Direkt und Heap. Diese beiden Arten von Puffern werden im Folgenden erläutert.
Auf dem JVM-Heap ist ein Puffer vom Typ Heap vorhanden. Das Recycling und Sortieren dieses Teils des Speichers ist dasselbe wie bei gewöhnlichen Objekten. Pufferobjekte vom Heap-Typ enthalten alle ein Array-Attribut, das einem Basisdatentyp entspricht (z. B. final **[] hb), und das Array ist der zugrunde liegende Puffer des Heap-Typs Buffer.
Der Puffer vom Heap-Typ kann jedoch nicht als Pufferparameter für direkte Systemaufrufe verwendet werden, hauptsächlich aus den folgenden zwei Gründen.
JVM kann den Puffer während der GC verschieben (kopieren und organisieren), und die Adresse des Puffers ist nicht festgelegt.
Bei Systemaufrufen muss der Puffer kontinuierlich sein, aber das Array ist möglicherweise nicht kontinuierlich (die JVM-Implementierung erfordert keine kontinuierliche).
Wenn Sie also einen Heap-Puffer für E/A verwenden, muss die JVM einen temporären Direktpuffer generieren, dann die Daten kopieren und dann den temporären Direktpuffer als Parameter verwenden ein Betriebssystemaufruf. Dies führt hauptsächlich aus zwei Gründen zu einer sehr geringen Effizienz:
Die Daten müssen vom Heap-Typ-Puffer in den vorübergehend erstellten Direktpuffer kopiert werden.
kann eine große Anzahl von Pufferobjekten generieren, wodurch die Häufigkeit der GC erhöht wird. Sie können also während E/A-Vorgängen eine Optimierung durchführen, indem Sie den Puffer wiederverwenden.
Der Puffer vom Typ „Direkt“ existiert nicht auf dem Heap, sondern ist ein kontinuierlicher Speicher, der direkt von der JVM über malloc zugewiesen wird Direkter Speicher: Die JVM verwendet den direkten Speicher als Puffer, wenn sie E/A-Systemaufrufe durchführt. -XX:MaxDirectMemorySize
, über diese Konfiguration können Sie die maximale direkte Speichergröße festlegen, die zugewiesen werden darf (der von MappedByteBuffer zugewiesene Speicher ist von dieser Konfiguration nicht betroffen).
Das direkte Speicherrecycling unterscheidet sich vom Heap-Speicherrecycling. Wenn der direkte Speicher nicht ordnungsgemäß verwendet wird, kann es leicht zu OutOfMemoryError kommen. JAVA bietet keine explizite Methode zum aktiven Freigeben von Direktspeicher. Die Klasse sun.misc.Unsafe kann direkte zugrunde liegende Speicheroperationen ausführen, und Direktspeicher kann über diese Klasse aktiv freigegeben und verwaltet werden. In ähnlicher Weise sollte auch der direkte Speicher wiederverwendet werden, um die Effizienz zu verbessern.
Das ist ein bisschen rückwärts: Eigentlich sollte MappedByteBuffer eine Unterklasse von DirectByteBuffer sein, aber um die Spezifikation klar zu halten und Einfacher, und aus Optimierungsgründen ist es einfacher, es umgekehrt zu machen. Dies funktioniert, weil DirectByteBuffer eine paketprivate Klasse ist.(Dieser Absatz stammt aus dem Quellcode von MappedByteBuffer)
Tatsächlich ist MappedByteBuffer ein zugeordneter Puffer (schauen Sie sich den virtuellen Speicher selbst an), aber DirectByteBuffer zeigt nur an, dass dieser Teil des Speichers ein kontinuierlicher Puffer ist, der von der JVM im direkten Speicherbereich zugewiesen wird und nicht unbedingt zugeordnet wird. Mit anderen Worten: MappedByteBuffer sollte eine Unterklasse von DirectByteBuffer sein, aber der Einfachheit halber und zur Optimierung wird MappedByteBuffer als übergeordnete Klasse von DirectByteBuffer verwendet. Obwohl MappedByteBuffer logischerweise eine Unterklasse von DirectByteBuffer sein sollte und der Speicher-GC von MappedByteBuffer dem GC des direkten Speichers ähnelt (anders als der Heap-GC), wird die Größe des zugewiesenen MappedByteBuffer durch -XX:MaxDirectMemorySize nicht beeinflusst Parameter.
MappedByteBuffer kapselt speicherzugeordnete Dateivorgänge, was bedeutet, dass nur Datei-E/A-Vorgänge ausgeführt werden können. MappedByteBuffer ist ein Mapping-Puffer, der auf der Grundlage von mmap generiert wird. Dieser Teil des Puffers wird der entsprechenden Dateiseite zugeordnet und gehört zum direkten Speicher im Benutzermodus. Der zugeordnete Puffer kann direkt über MappedByteBuffer bedient werden, und dieser Teil des Puffers wird zugeordnet Auf der Dateiseite vervollständigt das Betriebssystem das Schreiben und Schreiben von Dateien durch Aufrufen der entsprechenden Speicherseiten.
Get MappedByteBuffer über FileChannel.map(MapMode mode,long position, long size)
Der Generierungsprozess von MappedByteBuffer wird unten anhand des Quellcodes erläutert.
FileChannel.map
Quellcode:
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");//最大2Gif (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 sizeif (!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 requiredFileDescriptor dummy = new FileDescriptor();if ((!writable) || (imode == MAP_RO))return Util.newMappedByteBufferR(0, 0, dummy, null);elsereturn 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 mapSystem.gc();try { Thread.sleep(100); } catch (InterruptedException y) { Thread.currentThread().interrupt(); }try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError y) {// After a second OOME, failthrow 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引用文件的,这里得到文件idjint 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是宏定义,实际最后调用的是mmapmapAddress = 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) {//如果没有映射成功,直接抛出OutOfMemoryErrorJNU_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让操作系统直接释放这部分内存。
下面以ByteBuffer为例,说明Heap类型Buffer的细节。
该类型的Buffer可以通过下面方式产生:
ByteBuffer.allocate(int capacity)
ByteBuffer.wrap(byte[] array)
使用传入的数组作为底层缓冲区,变更数组会影响缓冲区,变更缓冲区也会影响数组。
ByteBuffer.wrap(byte[] array,int offset, int length)
使用传入的数组的一部分作为底层缓冲区,变更数组的对应部分会影响缓冲区,变更缓冲区也会影响数组。
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分配得到连续的缓冲区,这部分缓冲区可以直接作为缓冲区参数进行操作系统调用。
Das obige ist der detaillierte Inhalt vonEingehende Analyse des Java-Puffer-Quellcodes. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!