背景
最近在研究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;
}
}