Heim  >  Artikel  >  Java  >  Analysieren Sie den Buffer-Quellcode in Java

Analysieren Sie den Buffer-Quellcode in Java

零下一度
零下一度Original
2017-06-17 12:44:191751Durchsuche

In diesem Artikel werden hauptsächlich relevante Informationen zur Analyse des Pufferquellcodes in Java vorgestellt. Freunde, die sie benötigen, können sich auf

Analyse des Pufferquellcodes in Java

Buffer

Das Klassendiagramm von Buffer lautet wie folgt:

Zusätzlich zu Boolean gibt es auch andere grundlegende

Datentypen haben einen entsprechenden Puffer, aber nur ByteBuffer kann mit dem Kanal interagieren. Nur ByteBuffer kann direkte Puffer generieren, und Puffer anderer Datentypen können nur Puffer vom Heap-Typ generieren. ByteBuffer kann Ansichtspuffer anderer Datentypen generieren. Wenn ByteBuffer selbst direkt ist, ist jeder generierte Ansichtspuffer ebenfalls direkt.

Das Wesen von Puffern vom Typ „Direkt“ und „Heap“

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 Prototyp von read 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 vorhanden sein kontinuierlich.

Puffer ist in zwei Kategorien unterteilt: Direkt und Heap. Diese beiden Arten von Puffern werden im Folgenden erläutert.

Heap

Auf dem JVM-Heap ist ein Puffer vom Typ Heap vorhanden. Das Recycling und die Anordnung dieses Teils des Speichers sind die gleichen 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.


Allerdings kann der Heap-Typ-Puffer nicht direkt als Pufferparameter für 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.


  • Wenn das System aufgerufen wird, muss der Puffer kontinuierlich sein, aber das Array ist möglicherweise nicht kontinuierlich (die JVM-Implementierung erfordert keine kontinuierliche).


Wenn Sie also einen Puffer vom Typ Heap für E/A verwenden, muss die JVM einen temporären Puffer vom Typ Direct generieren, dann die Daten kopieren und dann die

des temporären Direct verwenden

Puffer wird als Parameter für Betriebssystemaufrufe verwendet. Dies führt hauptsächlich aus zwei Gründen zu einer sehr geringen Effizienz:

  1. Die Daten müssen vom Heap-Typ-Puffer in den vorübergehend erstellten Direktpuffer kopiert werden.

  2. kann eine große Anzahl von Pufferobjekten generieren, wodurch die Häufigkeit der GC erhöht wird. Daher kann während E/A-Vorgängen eine Optimierung durch Wiederverwendung des Puffers durchgeführt werden.


Direkt

Der Puffer vom Typ „Direkt“ existiert nicht auf dem Heap, sondern ist ein kontinuierliches Segment, das direkt von der JVM über malloc zugewiesen wird .Speicher, dieser Teil des Speichers wird zum Direktspeicher, und die JVM verwendet den Direktspeicher 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. Ebenso sollte auch der Direktspeicher wiederverwendet werden, um die Effizienz zu verbessern.

Beziehung zwischen MappedByteBuffer und DirectByteBuffer

Das ist ein bisschen rückwärts: Eigentlich sollte MappedByteBuffer eine Unterklasse von DirectByteBuffer sein, aber um die Spezifikation klar und einfach zu halten, und Aus Optimierungsgründen ist es einfacher, es umgekehrt zu machen, da DirectByteBuffer eine paketprivate Klasse ist (Dieser Absatz stammt aus dem Quellcode von MappedByteBuffer)


Eigentlich MappedByteBuffer gehört dazu, den Puffer zuzuordnen (sehen 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 werden muss. 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.

MappedByteBuffer

MappedByteBuffer über FileChannel.map abrufen (MapMode-Modus, lange Position, lange Größe) Der Generierungsprozess von MappedByteBuffer wird unten anhand des Quellcodes erläutert.

Quellcode von 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&#39;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));
    }
  }

Quellcode-Implementierung von 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);
}

Obwohl der Zise-Parameter von FileChannel.map() lang ist, beträgt die maximale Größe Integer.MAX_VALUE, was bedeutet, dass nur die maximale Größe des Speicherplatzes möglich ist auf 2G abgebildet werden. Tatsächlich kann der vom Betriebssystem bereitgestellte MMAP größeren Speicherplatz zuweisen, JAVA ist jedoch auf 2G beschränkt, und Puffer wie ByteBuffer können nur eine maximale Puffergröße von 2G zuweisen.

MappedByteBuffer ist ein durch mmap generierter Puffer. Dieser Teil des Puffers wird direkt vom Betriebssystem erstellt und verwaltet. Schließlich ermöglicht die JVM dem Betriebssystem, diesen Teil des Speichers direkt durch unmmap freizugeben .

Haep****Buffer

Im Folgenden wird ByteBuffer als Beispiel verwendet, um die Details des Puffers vom Heap-Typ zu veranschaulichen.

Diese Art von Puffer kann auf folgende Weise generiert werden:

  • ByteBuffer.allocate(int Capacity)

  • ByteBuffer.wrap(byte[] array) verwendet das eingehende Array als zugrunde liegenden Puffer. Eine Änderung des Arrays wirkt sich auf den Puffer aus, und eine Änderung des Puffers wirkt sich auch auf das Array aus.

  • ByteBuffer.wrap(byte[] array, int offset, int length)

Teil des übergebenen Arrays als zugrunde liegenden Puffer verwenden, Das Ändern des entsprechenden Teils des Arrays wirkt sich auf den Puffer aus, und das Ändern des Puffers wirkt sich auch auf das Array aus.

DirectByteBuffer

DirectByteBuffer kann nur von ByteBuffer.allocateDirect(int Capacity) generiert werden.

Der Quellcode von ByteBuffer.allocateDirect() lautet wie folgt:


 public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
  }

Der Quellcode von DirectByteBuffer() lautet wie folgt:


  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;
  }

Der Quellcode von unsafe.allocateMemory() befindet sich in openjdk/src/openjdk/hotspot/src/share/vm/prims/unsafe.cpp. Der spezifische Quellcode lautet wie folgt:


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

Die JVM weist über malloc einen kontinuierlichen Puffer zu. Dieser Teil des Puffers kann direkt als Pufferparameter für den Betrieb verwendet werden Systemaufrufe.

Das obige ist der detaillierte Inhalt vonAnalysieren Sie den Buffer-Quellcode in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn