ByteBuf是什么?
首先当前类是ByteBuf.java类,是netty缓存的抽象类。主要包含如下几个Index
0 <= readerIndex <= writerIndex <= capacity
包含的方法大致如下
setXxx //设置当前index对应的值
readXxx //读
WriteXxx //写
markXxx //标记当前index
resetXxx //reset到上面mark的index
其中AbstractByteBuf继承实现了某些具体的类,其继承关系如下:
主要实现了ByteBuf的 读写、mark、reset等方法,详情不细说,有兴趣自己看。其中里面很多方法都仅做声明(模板方法模式),如
继续往下跟可以发现如下结构图
ByteBuf分为safe和unsafe方式取数据
-
safe方式:通过内存地址+偏移量 获取数据
-
unsafe方式:数组+下标 或 jdk底层byteBuf api拿数据
ByteBuf可分为Heap(堆内存)、Direct(直接内存),往下根据源码可知道
-
Heap中使用的是byte[]数据,Direct中使用的是直接内存(依赖JDK底层的DirectByteBuf,需要自己手动释放)
Netty内存管理器
当前内存管理的继承关系图如下(ByteBufAllocatr是最上层):
接下来我们来看看UnpooledByteBufAllocator和PooledByteBufAllocator的实现和区别
1 均继承AbstractByteBufAllocator,但UnpooledByteBufAllocator更像是一个简化的内存分配器
2 先分析UnpooledByteBufAllocator,可发现其方法仅有6个如下截图(接下来大致分析一下):
先看其构造方法如下
// UnpooledByteBufAllocator.java
public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector) {
super(preferDirect); //preferDirect用于决定是否使用directBuf
this.disableLeakDetector = disableLeakDetector;
}
// AbstractByteBufAllocator.java
protected AbstractByteBufAllocator(boolean preferDirect) {
// 需要两者都为true才标记为使用directBuf,即需要为unsafe
directByDefault = preferDirect && PlatformDependent.hasUnsafe();
emptyBuf = new EmptyByteBuf(this);
}
接下来看一下是如何分配的(从下面就可以判断当前方法都会判断是否safe,然后分别创建不同的buf)
// UnpooledByteBufAllocator.java
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ? new UnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) : new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
ByteBuf buf = PlatformDependent.hasUnsafe() ?
UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}
3 上面简单的分析了Unpool的,接下来就不分析下Pool的(Pool的比较长,会单独出来讲解),注意的一点是Pool用的是
private final PoolThreadLocalCache threadCache;
PoolThreadLocalCache继承了FastThreadLocal,是netty对ThreadLocal的一种优化,其主要优化了如下,可参见:https://www.jianshu.com/p/3fc2fbac4bb7 + https://blog.csdn.net/lirenzuo/article/details/94495469
- 不使用jdk线性探测法的 Map,自定义了InternalThreadLocalMap,使用的是下标定位,所以更快,并且不会存在内存泄漏风险
下面继续讲解一下PoolByteBufAllocator.java中是如何分配内存的,其分配内存过程如下:
取一个实例化buffer的方法解析如下:
// PoolByteBufAllocator.java
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
PoolThreadCache cache = threadCache.get();
// 首先可看出heapArena是从cache中取出的,解析来我们可以看到分配的过程中涉及到了很多缓存
PoolArena<byte[]> heapArena = cache.heapArena;
ByteBuf buf;
if (heapArena != null) {
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
} else {
buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);
}
上面我们主要解析PoolArena是如何处理的,跟踪heapArena.allocate()方法如下
// PoolArena.java
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
// 第一步:实例化一个buffer
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
// 第二步:分配内存
allocate(cache, buf, reqCapacity);
return buf;
}
说明:上面主要分为两步,接下来会一个个简析
-
第一步:实例化一个buffer(跟踪到如下)
// PooledUnsafeDirectByteBuf.java
static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
// 他是从回收的对象池(RECYCLER.get())中直接复用,而不会重新new一个
PooledUnsafeDirectByteBuf buf = RECYCLER.get();
buf.reuse(maxCapacity);
return buf;
}
-
第二步:分配内存
-
分配内存这个会优先从缓存中进行分配(已使用并释放的),找一个差不多大小的缓存来分配掉,没有命中缓存才进行实际的内存分配。
-
// 以下的代码是优先找一个缓存进行分配的示例
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}