欢迎关注公众号:【爱编码】
如果有需要后台回复2019赠送1T的学习资料哦!!
简介
前文再续,书接上回。本文主要学习ByteBuf 的命中逻辑以及内存回收。
内存规格
tiny:总共32个规格, 均是16的整数倍, 0B, 16B, 32B, 48B, 64B, 80B, 96B…496B
small:4种规格, 512b, 1k, 2k, 4k
nomal:3种规格, 8k, 16k, 32k
PoolThreadCache中维护了三个缓存数组(实际上是六个, 这里仅仅以Direct为例, heap类型的逻辑是一样的): tinySubPageDirectCaches, smallSubPageDirectCaches, 和normalDirectCaches分别代表tiny类型, small类型和normal类型的缓存数组
final class PoolThreadCache {
private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
private final MemoryRegionCache<byte[]>[] normalHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
//省略很多代码 ...............
PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
checkPositiveOrZero(maxCachedBufferCapacity, "maxCachedBufferCapacity");
this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;
this.heapArena = heapArena;
this.directArena = directArena;
if (directArena != null) {
//初始化tinySubPageDirectCaches、smallSubPageDirectCaches 、normalDirectCaches
tinySubPageDirectCaches = createSubPageCaches(
tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
smallSubPageDirectCaches = createSubPageCaches(
smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small);
numShiftsNormalDirect = log2(directArena.pageSize);
normalDirectCaches = createNormalCaches(
normalCacheSize, maxCachedBufferCapacity, directArena);
directArena.numThreadCaches.getAndIncrement();
}
//省略很多代码 ...............
}
下面看一下以tiny类型的内存分配createSubPageCaches方法
private static <T> MemoryRegionCache<T>[] createSubPageCaches(
int cacheSize, int numCaches, SizeClass sizeClass) {
if (cacheSize > 0 && numCaches > 0) {
@SuppressWarnings("unchecked")
MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches];
for (int i = 0; i < cache.length; i++) {
// TODO: maybe use cacheSize / cache.length
cache[i] = new SubPageMemoryRegionCache<T>(cacheSize, sizeClass);
}
return cache;
} else {
return null;
}
}
这里创建了一个缓存数组, 这个缓存数组的长度,也就是numCaches, 在不同的类型, 这个长度不一样, tiny类型长度是32, small类型长度为4, normal类型长度为3。
缓存数组中每个节点代表一个缓存对象, 里面维护了一个队列, 队列大小由PooledByteBufAllocator类中的tinyCacheSize, smallCacheSize, normalCacheSize属性决定的。
每个缓存对象, 队列中缓存的ByteBuf大小是固定的, netty将每种缓冲区类型分成了不同长度规格, 而每个缓存中的队列缓存的ByteBuf的长度, 都是同一个规格的长度, 而缓冲区数组的长度, 就是规格的数量。
可能压根你就看不懂,反正我是没看懂。
Netty内存分配流程
摘自于xingdong大佬的文章:https://www.jianshu.com/p/1ce3bc2d7c5e
首先举一个生活中例子。假如我下了一个单,订购一块 N 字节的内存,并等待它的到达。怎样做到即时送达?
如果订购的内存是个小件(好比一块橡皮、一本书或是一个微波炉等),那么直接从同城仓库送出。
如果订购的内存是个大件(好比电视机、空调等),那么得从区域仓库(例如华东区仓库)送出。
如果订购的内存是个巨大件(好比汽车、轮船),那么得从全国仓库送出。
在 netty 类比以上的物流系统中
同城仓库相当于 PoolThreadCache —— 线程独有的内存仓库;
区域仓库相当于PoolArean —— 几个线程共享的内存仓库;
全国仓库相当于全局变量指向的内存仓库,为所有线程可用。
在 netty 中,整块批发内存,之后或拆开零售,或整块出售。整块批发的内存叫做 chunk,对于小件和大件订单,则进一步拆成 run。
Chunk 的大小为 16MB(可调)或其倍数,如果是堆外内存、那么根据16M对齐;而 run 大小为页大小(8k)的整数倍。
netty中整个内存分配可以用以下图来展示,源码自己照着这图去看比较好点。
ByteBuf回收
堆外内存是不受jvm垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf操作时, 使用完毕要对对象进行回收。
以PooledUnsafeDirectByteBuf为例:
1.PooledUnsafeDirectByteBuf中内存释放的入口方法是其父类AbstractReferenceCountedByteBuf中的release方法:
@Override
public boolean release() {
return handleRelease(updater.release(this));
}
private boolean handleRelease(boolean result) {
if (result) {
deallocate();
}
return result;
}
2.updater.release(this)方法的实际调用如下。这个方法的工作猜测应该是是判断一下这个ByteBuf 是否可以回收,再三尝试确认是否还有被线程调用的等操作。
反正我又没看懂,还想大佬们解答,网上没有找到。。。
public abstract class ReferenceCountUpdater<T extends ReferenceCounted> {
public final boolean release(T instance) {
//记它被标记回收的次数
int rawCnt = nonVolatileRawCnt(instance);
return rawCnt == 2 ? tryFinalRelease0(instance, 2) || retryRelease0(instance, 1)
: nonFinalRelease0(instance, 1, rawCnt, toLiveRealRefCnt(rawCnt, 1));
}
private int nonVolatileRawCnt(T instance) {
// TODO: Once we compile against later versions of Java we can replace the Unsafe usage here by varhandles.
final long offset = unsafeOffset();
return offset != -1 ? PlatformDependent.getInt(instance, offset) : updater().get(instance);
}
private boolean tryFinalRelease0(T instance, int expectRawCnt) {
return updater().compareAndSet(instance, expectRawCnt, 1); // any odd number will work
}
private boolean retryRelease0(T instance, int decrement) {
for (;;) {
int rawCnt = updater().get(instance), realCnt = toLiveRealRefCnt(rawCnt, decrement);
if (decrement == realCnt) {
if (tryFinalRelease0(instance, rawCnt)) {
return true;
}
} else if (decrement < realCnt) {
// all changes to the raw count are 2x the "real" change
if (updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
} else {
throw new IllegalReferenceCountException(realCnt, -decrement);
}
Thread.yield(); // this benefits throughput under high contention
}
}
private boolean nonFinalRelease0(T instance, int decrement, int rawCnt, int realCnt) {
if (decrement < realCnt
// all changes to the raw count are 2x the "real" change - overflow is OK
&& updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
return retryRelease0(instance, decrement);
}
//省略了很多代码。。。
}
3.回到第一步的handleRelease判断是否需要回收,回收的基本逻辑如下代码:
this.handle = -1表示当前的ByteBuf不再指向任何一块内存
memory = null这里将memory也设置为null
chunk.arena.free(chunk, handle, maxLength, cache)这一步是将ByteBuf的内存进行释放
recycle()是将对象放入的对象回收站, 循环利用
abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
@Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache);
tmpNioBuf = null;
chunk = null;
recycle();
}
}
}
4.chunk.arena.free(chunk, handle, maxLength, cache)这一步是将ByteBuf的内存进行释放.
void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) {
if (chunk.unpooled) {
int size = chunk.chunkSize();
destroyChunk(chunk);
activeBytesHuge.add(-size);
deallocationsHuge.increment();
} else {
SizeClass sizeClass = sizeClass(normCapacity);
if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) {
// cached so not free it.
return;
}
freeChunk(chunk, handle, sizeClass, nioBuffer, false);
}
}
5.最后就是调用Recycler中的recycle()是将对象放入的对象回收站, 循环利用。
public abstract class Recycler<T> {
@Override
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
Stack<?> stack = this.stack;
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
}
总结
主要学习了ByteBuf 的内存分配以及内存回收。下回学习Netty的大动脉pipeline
参考文章
https://www.cnblogs.com/xiangnan6122/p/10202191.html
https://www.jianshu.com/u/fc9c660e9843
最后
如果对 Java、大数据感兴趣请长按二维码关注一波,我会努力带给你们价值。觉得对你哪怕有一丁点帮助的请帮忙点个赞或者转发哦。
关注公众号**【爱编码】,回复2019**有相关资料哦。