背景
最近在研究netty的源代码,发现netty的内存管理都是用jdk的ByteBuffer。为了更深入的了解bytebuffer,因此有了这篇文章
ByteBuffer的基本组成
ByteBuffer 的基本函数http://kakajw.iteye.com/blog/1797073
ByteBuffer分为两类DirectBuffer和HeapBuffer。DirectBuffer速度快于HeapBuffer。DirectBuffer可以直接将数据输出到终端,避免内存拷贝。DirectBuffer的分配开销大于HeapBuffer。但是DirectBuffer不用GC。因此DirectBuffer一般用于大块,且长期使用的内存。因此特别适用于Netty的内存分配模型。详细的说明如下:
* <p> A byte buffer is either <i>direct</i> or <i>non-direct</i>. Given a * direct byte buffer, the Java virtual machine will make a best effort to * perform native I/O operations directly upon it. That is, it will attempt to * avoid copying the buffer's content to (or from) an intermediate buffer * before (or after) each invocation of one of the underlying operating * system's native I/O operations. * * <p> A direct byte buffer may be created by invoking the {@link * #allocateDirect(int) allocateDirect} factory method of this class. The * buffers returned by this method typically have somewhat higher allocation * and deallocation costs than non-direct buffers. The contents of direct * buffers may reside outside of the normal garbage-collected heap, and so * their impact upon the memory footprint of an application might not be * obvious. It is therefore recommended that direct buffers be allocated * primarily for large, long-lived buffers that are subject to the underlying * system's native I/O operations. In general it is best to allocate direct * buffers only when they yield a measureable gain in program performance. * * <p> A direct byte buffer may also be created by {@link * java.nio.channels.FileChannel#map </code>mapping<code>} a region of a file * directly into memory. An implementation of the Java platform may optionally * support the creation of direct byte buffers from native code via JNI. If an * instance of one of these kinds of buffers refers to an inaccessible region * of memory then an attempt to access that region will not change the buffer's * content and will cause an unspecified exception to be thrown either at the * time of the access or at some later time. * * <p> Whether a byte buffer is direct or non-direct may be determined by * invoking its {@link #isDirect isDirect} method. This method is provided so * that explicit buffer management can be done in performance-critical code.
ByteBuffer继承于Buffer对象,Buffer的成员变量和方法如下:
public abstract class Buffer { // Invariants: mark <= position <= limit <= capacity private int mark = -1; //标记的位置 private int position = 0; //当前位置 private int limit; //限制大小 private int capacity; //总的内存容量 // Used only by direct buffers // NOTE: hoisted here for speed in JNI GetDirectBufferAddress long address; /** * Sets this buffer's mark at its position. </p> * * @return This buffer */ public final Buffer mark() { mark = position; return this; } /** * Resets this buffer's position to the previously-marked position. * * <p> Invoking this method neither changes nor discards the mark's * value. </p> * * @return This buffer * * @throws InvalidMarkException * If the mark has not been set */ public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; } /** * Clears this buffer. The position is set to zero, the limit is set to * the capacity, and the mark is discarded. * * <p> Invoke this method before using a sequence of channel-read or * <i>put</i> operations to fill this buffer. For example: * * <blockquote><pre> * buf.clear(); // Prepare buffer for reading * in.read(buf); // Read data</pre></blockquote> * * <p> This method does not actually erase the data in the buffer, but it * is named as if it did because it will most often be used in situations * in which that might as well be the case. </p> * * @return This buffer */ public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } /** * Flips this buffer. The limit is set to the current position and then * the position is set to zero. If the mark is defined then it is * discarded. * * <p> After a sequence of channel-read or <i>put</i> operations, invoke * this method to prepare for a sequence of channel-write or relative * <i>get</i> operations. For example: * * <blockquote><pre> * buf.put(magic); // Prepend header * in.read(buf); // Read data into rest of buffer * buf.flip(); // Flip buffer * out.write(buf); // Write header + data to channel</pre></blockquote> * * <p> This method is often used in conjunction with the {@link * java.nio.ByteBuffer#compact compact} method when transferring data from * one place to another. </p> * * @return This buffer */ public final Buffer flip() { limit = position; position = 0; mark = -1; return this; } /** * Rewinds this buffer. The position is set to zero and the mark is * discarded. * * <p> Invoke this method before a sequence of channel-write or <i>get</i> * operations, assuming that the limit has already been set * appropriately. For example: * * <blockquote><pre> * out.write(buf); // Write remaining data * buf.rewind(); // Rewind buffer * buf.get(array); // Copy data into array</pre></blockquote> * * @return This buffer */ public final Buffer rewind() { position = 0; mark = -1; return this; } }
ByteBuffer的主要接口
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> { // These fields are declared here rather than in Heap-X-Buffer in order to // reduce the number of virtual method invocations needed to access these // values, which is especially costly when coding small buffers. // final byte[] hb; // Non-null only for heap buffers final int offset; boolean isReadOnly; // Valid only for heap buffers /** * Creates a new byte buffer whose content is a shared subsequence of * this buffer's content. * * <p> The content of the new buffer will start at this buffer's current * position. Changes to this buffer's content will be visible in the new * buffer, and vice versa; the two buffers' position, limit, and mark * values will be independent. * * <p> The new buffer's position will be zero, its capacity and its limit * will be the number of bytes remaining in this buffer, and its mark * will be undefined. The new buffer will be direct if, and only if, this * buffer is direct, and it will be read-only if, and only if, this buffer * is read-only. </p> * * @return The new byte buffer */ public abstract ByteBuffer slice(); /** * Creates a new byte buffer that shares this buffer's content. * * <p> The content of the new buffer will be that of this buffer. Changes * to this buffer's content will be visible in the new buffer, and vice * versa; the two buffers' position, limit, and mark values will be * independent. * * <p> The new buffer's capacity, limit, position, and mark values will be * identical to those of this buffer. The new buffer will be direct if, * and only if, this buffer is direct, and it will be read-only if, and * only if, this buffer is read-only. </p> * * @return The new byte buffer */ public abstract ByteBuffer duplicate(); /** * Creates a new, read-only byte buffer that shares this buffer's * content. * * <p> The content of the new buffer will be that of this buffer. Changes * to this buffer's content will be visible in the new buffer; the new * buffer itself, however, will be read-only and will not allow the shared * content to be modified. The two buffers' position, limit, and mark * values will be independent. * * <p> The new buffer's capacity, limit, position, and mark values will be * identical to those of this buffer. * * <p> If this buffer is itself read-only then this method behaves in * exactly the same way as the {@link #duplicate duplicate} method. </p> * * @return The new, read-only byte buffer */ public abstract ByteBuffer asReadOnlyBuffer(); }
MappedByteBuffer 实现了ByteBuffer接口,内存映射到文件
/** * A direct byte buffer whose content is a memory-mapped region of a file. * * <p> Mapped byte buffers are created via the {@link * java.nio.channels.FileChannel#map FileChannel.map} method. This class * extends the {@link ByteBuffer} class with operations that are specific to * memory-mapped file regions. * * <p> A mapped byte buffer and the file mapping that it represents remain * valid until the buffer itself is garbage-collected. * * <p> The content of a mapped byte buffer can change at any time, for example * if the content of the corresponding region of the mapped file is changed by * this program or another. Whether or not such changes occur, and when they * occur, is operating-system dependent and therefore unspecified. * * <a name="inaccess"><p> All or part of a mapped byte buffer may become * inaccessible at any time, for example if the mapped file is truncated. An * attempt to access an inaccessible region of a mapped byte buffer will not * change the buffer's content and will cause an unspecified exception to be * thrown either at the time of the access or at some later time. It is * therefore strongly recommended that appropriate precautions be taken to * avoid the manipulation of a mapped file by this program, or by a * concurrently running program, except to read or write the file's content. * * <p> Mapped byte buffers otherwise behave no differently than ordinary direct * byte buffers. </p> * * * @author Mark Reinhold * @author JSR-51 Expert Group * @since 1.4 */ public abstract class MappedByteBuffer extends ByteBuffer { // This is a little bit backwards: By rights MappedByteBuffer should be a // subclass of DirectByteBuffer, but to keep the spec clear and simple, and // for optimization purposes, it's easier to do it the other way around. // This works because DirectByteBuffer is a package-private class. // For mapped buffers, a FileDescriptor that may be used for mapping // operations if valid; null if the buffer is not mapped. private final FileDescriptor fd; /** * Tells whether or not this buffer's content is resident in physical * memory. * * <p> A return value of <tt>true</tt> implies that it is highly likely * that all of the data in this buffer is resident in physical memory and * may therefore be accessed without incurring any virtual-memory page * faults or I/O operations. A return value of <tt>false</tt> does not * necessarily imply that the buffer's content is not resident in physical * memory. * * <p> The returned value is a hint, rather than a guarantee, because the * underlying operating system may have paged out some of the buffer's data * by the time that an invocation of this method returns. </p> * * @return <tt>true</tt> if it is likely that this buffer's content * is resident in physical memory */ public final boolean isLoaded() { checkMapped(); if ((address == 0) || (capacity() == 0)) return true; long offset = mappingOffset(); long length = mappingLength(offset); return isLoaded0(mappingAddress(offset), length, Bits.pageCount(length)); } // not used, but a potential target for a store, see load() for details. private static byte unused; /** * Loads this buffer's content into physical memory. * * <p> This method makes a best effort to ensure that, when it returns, * this buffer's content is resident in physical memory. Invoking this * method may cause some number of page faults and I/O operations to * occur. </p> * * @return This buffer */ public final MappedByteBuffer load() { checkMapped(); if ((address == 0) || (capacity() == 0)) return this; long offset = mappingOffset(); long length = mappingLength(offset); load0(mappingAddress(offset), length); // Read a byte from each page to bring it into memory. A checksum // is computed as we go along to prevent the compiler from otherwise // considering the loop as dead code. Unsafe unsafe = Unsafe.getUnsafe(); int ps = Bits.pageSize(); int count = Bits.pageCount(length); long a = mappingAddress(offset); byte x = 0; for (int i=0; i<count; i++) { x ^= unsafe.getByte(a); a += ps; } if (unused != 0) unused = x; return this; } /** * Forces any changes made to this buffer's content to be written to the * storage device containing the mapped file. * * <p> If the file mapped into this buffer resides on a local storage * device then when this method returns it is guaranteed that all changes * made to the buffer since it was created, or since this method was last * invoked, will have been written to that device. * * <p> If the file does not reside on a local device then no such guarantee * is made. * * <p> If this buffer was not mapped in read/write mode ({@link * java.nio.channels.FileChannel.MapMode#READ_WRITE}) then invoking this * method has no effect. </p> * * @return This buffer */ public final MappedByteBuffer force() { checkMapped(); if ((address != 0) && (capacity() != 0)) { long offset = mappingOffset(); force0(fd, mappingAddress(offset), mappingLength(offset)); } return this; } }
DirectByteBuffer继承MappedByteBuffer
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer { // Cached unsafe-access object protected static final Unsafe unsafe = Bits.unsafe(); // Cached array base offset private static final long arrayBaseOffset = (long)unsafe.arrayBaseOffset(byte[].class); // Cached unaligned-access capability protected static final boolean unaligned = Bits.unaligned(); // Base address, used in all indexing calculations // NOTE: moved up to Buffer.java for speed in JNI GetDirectBufferAddress // protected long address; // An object attached to this buffer. If this buffer is a view of another // buffer then we use this field to keep a reference to that buffer to // ensure that its memory isn't freed before we are done with it. private final Object att; DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { //分配内存空间 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } //设置内存为0 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; } //用剩下的空间创建一个直接缓存 public ByteBuffer slice() { int pos = this.position(); int lim = this.limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); int off = (pos << 0); assert (off >= 0); return new DirectByteBuffer(this, -1, 0, rem, rem, off); } //拷贝一个直接缓存 public ByteBuffer duplicate() { return new DirectByteBuffer(this, this.markValue(), this.position(), this.limit(), this.capacity(), 0); } private short getShort(long a) { if (unaligned) { //不对齐的系统,不能通过一次获取多个字节 short x = unsafe.getShort(a); return (nativeByteOrder ? x : Bits.swap(x)); } return Bits.getShort(a, bigEndian); } public short getShort() { return getShort(ix(nextGetIndex((1 << 1)))); } private ByteBuffer putShort(long a, short x) { if (unaligned) { short y = (x); unsafe.putShort(a, (nativeByteOrder ? y : Bits.swap(y))); } else { Bits.putShort(a, x, bigEndian); } return this; } public ByteBuffer putShort(short x) { putShort(ix(nextPutIndex((1 << 1))), x); return this; } }
HeapByteBuffer是堆缓存,通过new byte[cap]方法分配。
class HeapByteBuffer extends ByteBuffer { // For speed these fields are actually declared in X-Buffer; // these declarations are here as documentation /* protected final byte[] hb; protected final int offset; */ HeapByteBuffer(int cap, int lim) { // package-private super(-1, 0, lim, cap, new byte[cap], 0); /* hb = new byte[cap]; offset = 0; */ } public short getShort() { return Bits.getShort(this, ix(nextGetIndex(2)), bigEndian); } public short getShort(int i) { return Bits.getShort(this, ix(checkIndex(i, 2)), bigEndian); } public ByteBuffer putShort(short x) { Bits.putShort(this, ix(nextPutIndex(2)), x, bigEndian); return this; } public ByteBuffer putShort(int i, short x) { Bits.putShort(this, ix(checkIndex(i, 2)), x, bigEndian); return this; } }