傻大方提要:【绝!真就一文全懂!Netty线程模型+启动详细分析+内存管理( 六 )】这是中间状态的Buffer , 可以通过调用discardReadBytes方法来回收已读区域 文章插图 通过clear方法清楚指针状态 文章插图 对比ByteBuffer , 使用ByteBuf读的时候...
按关键词阅读:
这是中间状态的Buffer , 可以通过调用discardReadBytes方法来回收已读区域
文章插图
通过clear方法清楚指针状态
文章插图
对比ByteBuffer , 使用ByteBuf读的时候仅仅依赖readerIndex指针 , 写的时候仅仅依赖writerIndex指针 , 不需每次读写之前调用对应的方法 , 而且没有必须一次读完的限制 。
内存管理零拷贝当JVM堆内存上的数据需要和IO设备进行I/O操作时 , 会将JVM堆上所维护的byte[]拷贝至堆外内存(一般是通过C/C++分配的内存) , 然后堆外内存直接和IO设备交互 。 这是因为JVM需要进行GC , 如果IO设备直接和JVM堆上的数据进行交互 , 这个时候JVM进行了GC , 那么有可能会导致没有被回收的数据进行了压缩 , 位置被移动到了连续的存储区域 , 这样会导致正在进行的IO操作相关的数据全部乱套 。
NIO可以使用native 函数库直接分配堆外内存 , 然后通过一个存储在堆上的DirectByteBuffer 对象作为这块内存的引用进行操作 , 避免了在Java堆和Native堆中来回复制数据 。
内存泄漏从堆中分配的缓冲区HeapByteBuffer为普通的Java对象 , 生命周期与普通的Java对象一样 , 当不再被引用时 , Buffer对象会被回收 。 而直接缓冲区(DirectByteBuffer)为堆外内存 , 并不在Java堆中 , 也不能被JVM垃圾回收 。 由于直接缓冲区在JVM里被包装进Java对象DirectByteBuffer中 , 当它的包装类被垃圾回收时 , 会调用相应的JNI方法释放堆外内存 , 所以堆外内存的释放也依赖于JVM中DirectByteBuffer对象的回收 。
由于垃圾回收本身成本较高 , 一般JVM在堆内存未耗尽时 , 不会进行垃圾回收操作 。 如果不断分配本地内存 , 堆内存很少使用 , 那么JVM就不需要执行GC , DirectByteBuffer对象们就不会被回收 , 这时候堆内存充足 , 但本地内存可能已经使用光了 , 再次尝试分配本地内存就会出现OutOfMemoryError , 那程序就直接崩溃了 。
ByteBuf引用计数public interface ReferenceCounted {int refCnt();ReferenceCounted retain();ReferenceCounted retain(int var1);ReferenceCounted touch();ReferenceCounted touch(Object var1);boolean release();boolean release(int var1);}ByteBuf扩展了ReferenceCountered接口, 这个接口定义的功能主要是引用计数 。
当 ByteBuf 引用+1的时候 , 需要调用 retain() 来让refCnt + 1 , 当Buffer引用数-1的时候需要调用 release() 来让 refCnt - 1 。 当refCnt变为0的时候Netty为pooled和unpooled的不同buffer提供了不同的实现 , 通常对于非内存池的用法 , Netty把Buffer的内存回收交给了垃圾回收器 , 对于内存池的用法 , Netty对内存的回收实际上是回收到内存池内 , 以提供下一次的申请所使用 。
池化如果对于Buffer的使用都基于直接内存实现的话 , 将会大大提高I/O效率 , 然而直接内存空间的申请比堆内存要消耗更高的性能 。
因此Netty结合引用计数实现了PolledBuffer , 即池化的用法 , 当引用计数等于0的时候 , Netty将Buffer回收至池中 , 在下一次申请Buffer的时刻会被复用 。
堆内存和直接内存的池化实现分别是PooledHeapByteBuf和PooledDirectByteBuf , 在各自的实现中都维护着一个Recycler。 Recycler是一个抽象类 , 向外部提供了两个公共方法get和recycle分别用于从对象池中获取对象和回收对象 。
以PooledHeapByteBuf为例 , 新建PooledHeapByteBuf对象时
static PooledHeapByteBuf newInstance(int maxCapacity) {PooledHeapByteBuf buf = (PooledHeapByteBuf)RECYCLER.get();buf.reuse(maxCapacity);return buf;}当Buffer引用数 -1时
public boolean release(int decrement) {return this.release0(ObjectUtil.checkPositive(decrement, "decrement"));}private boolean release0(int decrement) {int oldRef = refCntUpdater.getAndAdd(this, -decrement);if (oldRef == decrement) {this.deallocate();return true;} else if (oldRef >= decrement} else {refCntUpdater.getAndAdd(this, decrement);throw new IllegalReferenceCountException(oldRef, -decrement);}}PooledByteBuf.class
protected final void deallocate() {if (this.handle >= 0L) {long handle = this.handle;this.handle = -1L;this.memory = null;this.tmpNioBuf = null;this.chunk.arena.free(this.chunk, handle, this.maxLength, this.cache);this.chunk = null;this.recycle();}}private void recycle() {this.recyclerHandle.recycle(this);}半包读写TCP是个"流"协议 , 所谓流 , 就是没有界限没有分割的一串数据 。 TCP会根据缓冲区的实际情况进行包划分 , 一个完整的包可能会拆分成多个包进行发送 , 也用可能把多个小包封装成一个大的数据包发送 。 这就是TCP粘包/拆包 。
稿源:(未知)
【傻大方】网址:http://www.shadafang.com/c/111J293N2020.html
标题:绝!真就一文全懂!Netty线程模型+启动详细分析+内存管理( 六 )