说在前面
前期回顾
sharding-jdbc源码解析 更新完毕
spring源码解析 更新完毕
spring-mvc源码解析 更新完毕
spring-tx源码解析 更新完毕
spring-boot源码解析 更新完毕
rocketmq源码解析 更新完毕
dubbbo源码解析 更新完毕
netty源码解析 更新完毕
spring源码架构更新完毕
spring-mvc源码架构更新完毕
spring-boot源码架构更新完毕
github https://github.com/tianheframe
sharding-jdbc源码解析 更新完毕
rocketmq源码解析 更新完毕
seata 源码解析 更新完毕
dubbo 源码解析 更新完毕
netty 源码解析 更新完毕
源码解析
netty的精华之处就是内存池的设计和reactor线程模型设计,本次主要介绍下内存池的原理,内存池的好处是可以重复使用堆内存区域,降低内存申请的频率同时也降低了JVM GC的频率,netty一般用来做设计海量的分布式场景下的底层网络通讯技术模块实现,所以内存池就显得尤为重要了,进一步提高系统吞吐量
我们还是从源码的维度去解析,io.netty.buffer.Unpooled#buffer(int)方法创建一个堆缓冲区
public static ByteBuf buffer(int initialCapacity) {
return ALLOC.heapBuffer(initialCapacity);}
io.netty.buffer.AbstractByteBufAllocator#heapBuffer(int, int)
@Override public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf; }// 验证参数合法性,最大的堆缓冲区容量是int最大值字节 validate(initialCapacity, maxCapacity); return newHeapBuffer(initialCapacity, maxCapacity); }
基于内存池创建堆缓冲区这个方法io.netty.buffer.PooledByteBufAllocator#newHeapBuffer
@Override protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
// 先从本地缓存内存中取 PoolThreadCache cache = threadCache.get();// 获取堆区域 PoolArena<byte[]> heapArena = cache.heapArena; final ByteBuf buf; if (heapArena != null) {
// 从这块堆区域中分配缓冲区 buf = heapArena.allocate(cache, initialCapacity, maxCapacity);// 否则就不用内存池直接分配内存 } else {
buf = PlatformDependent.hasUnsafe() ? new UnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) : new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity); }// 检测是否有内存泄漏 return toLeakAwareBuffer(buf); }
基于内存池区域分配缓冲区io.netty.buffer.PoolArena#allocate(io.netty.buffer.PoolThreadCache, int, int)
PooledByteBufallocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
// 创建基于内存池的byteBuf PooledByteBuf buf = newByteBuf(maxCapacity);// 分配内存 allocate(cache, buf, reqCapacity); return buf; }
基于内存池创建byteBuf有两种方式,堆内存、直接内存
基于内存池分配堆内存io.netty.buffer.PoolArena.HeapArena#newByteBuf
@Overrideprotected PooledByteBuf<byte[]> newByteBuf(int maxCapacity) {
return HAS_UNSAFE ? PooledUnsafeHeapByteBuf.newUnsafeInstance(maxCapacity) : PooledHeapByteBuf.newInstance(maxCapacity);}
io.netty.buffer.PooledHeapByteBuf#newInstance创建PooledHeapByteBuf对象
static PooledHeapByteBuf newInstance(int maxCapacity) {
// 从对象回收器中回收一个对象 PooledHeapByteBuf buf = RECYCLER.get(); buf.reuse(maxCapacity); return buf; }
io.netty.buffer.PooledByteBuf#reuse重写设置一些参数
final void reuse(int maxCapacity) {
maxCapacity(maxCapacity);// 设置引用计数,这里是线程安全的 setRefCnt(1);// 设置读写索引 setIndex0(0, 0); discardMarks(); }
通过上面源码跟踪发现用内存池分配缓冲区有三个主要的点
1、一个是从本地线程缓冲池中找到poolThreadCache,再从poolThreadCache中找到heapArena,如果有存在headArena就是开始分配内存
2、分配内存的时候先从对象回收器RECYCLER中回收一个对象PooledByteBuf,类似一个对象池的作用,在进行分配内存,对象可以循环利用,提供内存分配效率和JVM gc效率,这个对象回收器是基于引用计数方式实现的
3、分配内存
4、0 copy
PoolThreadCache
充当分配线程的缓存,这里的设计可以提升对象内存分配的效率,假设你想在给对象创建时分配对象的时候怎么提升对象分配的效率呢,就是快速拿到可用内存,那如果是高并发情况下,因为netty就是为分布式、高并发场景而设计的,所以高并发场景下给对象分配内存空间是一种常态,那么这种场景下应该怎么提升对象内存的分配效率呢,那我想到的就是减少多线程操作一块内存区域的锁争用,还有就是在标记那一块内存未使用,那一块已经使用了在维护这个内存使用记录的时候采用cas操作,再用的话就是给每个线程预先分配一块内存,在每个对象需要进行对象创建分配内存的时候先从本地线程缓存中获取内存,获取不到在从堆中申请内存,这种方式可以一定程度上减少多线程之间操作一块内存的锁争用,也能很好的提升对象内存分配的并发度,很大程度上能提升对象内存分配的性能,PoolThreadCache正是基于这种方式实现的。
那接下来我们跟着源码看下netty是怎么设计线程本地缓存的,它是基于PoolThreadLocalCache实现的,PoolThreadLocalCache集成了FastThreadLocal,FastThreadLocal是netty扩展了自己的threadLocal,为什么名字是Fast开头呢,从FastThreadLocal访问时可以产生很好的性能,底层是用数组实现的,