Netty 系列目录 (http://www.javashuo.com/article/p-hskusway-em.html)html
(1) Buffer 分配java
// 分配非直接缓冲区 public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); } // 分配直接缓冲区 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
(2) ByteBuffer 内存存储数组
public abstract class Buffer { // Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity; // 直接缓冲区指向系统内核的一个地址,之因此放到父类中是为了加快 JNI 的访问速度 long address; } public abstract class ByteBuffer extends Buffer { // 非直接缓冲区在 JVM 内存上分配一个字节数组 final byte[] hb; }
(3) DirectByteBufferapp
DirectByteBuffer(int cap) { 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; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null } public byte get() { return ((unsafe.getByte(ix(nextGetIndex())))); } public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x))); return this; }
DirectByteBuffer 对直接缓冲区的操做都委托给了类 sun.misc.Unsafe,Unsafe 都是一些本地方法 native。函数
public final class Unsafe { public native long allocateMemory(long bytes); public native byte getByte(long address); public native void putByte(long address, byte x); }
使用直接缓冲区能够避免用户空间和系统空间之间的拷贝过程,即零拷贝。this
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("3.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // 方式一:内存映射文件,直接缓冲区 MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); //只有 READ_WRITE,没有 WRITE,所以 outChannel 也要加上 READ MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); byte[] bytes = new byte[inMappedBuf.limit()]; inMappedBuf.get(bytes); outMappedBuf.put(bytes); // 方式二:transferTo 也是使用直接缓冲区 //inChannel.transferTo(0, inChannel.size(), outChannel); //outChannel.transferFrom(inChannel, 0, inChannel.size());
Java NIO中的 direct buffer(主要是 DirectByteBuffer)实际上是分两部分的:spa
Java | native | DirectByteBuffer | malloc'd [ address ] -+-> [ data ]
其中 DirectByteBuffer 自身是一个 Java 对象,在 Java 堆中;而这个对象中有个 long 类型字段 address,记录着一块调用 malloc() 申请到的 native memory。netty
FileChannel 的 read(ByteBuffer dst) 函数,write(ByteBuffer src) 函数中,若是传入的参数是 HeapBuffer 类型,则会临时申请一块 DirectBuffer,进行数据拷贝,而不是直接进行数据传输,这是出于什么缘由?code
// OpenJDK 的 sun.nio.ch.IOUtil static int write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd) throws IOException { if (src instanceof DirectBuffer) return writeFromNativeBuffer(fd, src, position, nd); // Substitute a native buffer int pos = src.position(); int lim = src.limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); ByteBuffer bb = Util.getTemporaryDirectBuffer(rem); try { bb.put(src); bb.flip(); // Do not update src until we see how many bytes were written src.position(pos); int n = writeFromNativeBuffer(fd, bb, position, nd); if (n > 0) { // now update src src.position(pos + n); } return n; } finally { Util.offerFirstTemporaryDirectBuffer(bb); } }
这里实际上是在迁就 OpenJDK 里的 HotSpot VM 的一点实现细节。htm
HotSpot VM 里的 GC 除了 CMS 以外都是要移动对象的,是所谓 “compacting GC”。
若是要把一个 Java 里的 byte[] 对象的引用传给 native 代码,让 native 代码直接访问数组的内容的话,就必需要保证 native 代码在访问的时候这个 byte[] 对象不能被移动,也就是要被“pin”(钉)住。
惋惜 HotSpot VM 出于一些取舍而决定不实现单个对象层面的 object pinning,要 pin 的话就得暂时禁用 GC ——也就等于把整个 Java 堆都给 pin 住。HotSpot VM 对 JNI 的 Critical 系 API 就是这样实现的。这用起来就不那么顺手。
因此 Oracle/Sun JDK / OpenJDK 的这个地方就用了点绕弯的作法。 它假设把 HeapByteBuffer 背后的 byte[] 里的内容拷贝一次是一个时间开销能够接受的操做,同时假设真正的 I/O 多是一个很慢的操做。
因而它就先把 HeapByteBuffer 背后的 byte[] 的内容拷贝到一个 DirectByteBuffer 背后的 native memory 去,这个拷贝会涉及 sun.misc.Unsafe.copyMemory() 的调用,背后是相似 memcpy() 的实现。这个操做本质上是会在整个拷贝过程当中暂时不容许发生 GC 的,虽然实现方式跟 JNI 的 Critical 系 API 不太同样。(具体来讲是 Unsafe.copyMemory() 是 HotSpot VM 的一个 intrinsic 方法,中间没有 safepoint 因此 GC 没法发生)。
而后数据被拷贝到 native memory 以后就好办了,就去作真正的 I/O,把 DirectByteBuffer 背后的 native memory 地址传给真正作 I/O 的函数。这边就不须要再去访问 Java 对象去读写要作 I/O 的数据了。
参考:
天天用心记录一点点。内容也许不重要,但习惯很重要!