Netty源码分析第5章(ByteBuf)---->第9节: ByteBuf回收
Netty源码分析第五章: ByteBuf
第九节: ByteBuf回收
之前的章节我们提到过, 堆外内存是不受jvm垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf操作时, 使用完毕要对对象进行回收, 这一小节, 就以PooledUnsafeDirectByteBuf为例讲解有关内存分配的相关逻辑
PooledUnsafeDirectByteBuf中内存释放的入口方法是其父类AbstractReferenceCountedByteBuf中的release方法:
@Override
public boolean release() {
return release0(1);
}
这里调用了release0, 跟进去:
private boolean release0(int decrement) {
for (;;) {
int refCnt = this.refCnt;
if (refCnt < decrement) {
throw new IllegalReferenceCountException(refCnt, -decrement);
}
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
if (refCnt == decrement) {
deallocate();
return true;
}
return false;
}
}
}
if (refCnt == decrement) 中判断当前byteBuf是否没有被引用了, 如果没有被引用, 则通过deallocate()方法进行释放
因为我们是以PooledUnsafeDirectByteBuf为例, 所以这里会调用其父类PooledByteBuf的deallocate方法:
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, handle, maxLength, cache);
recycle();
}
}
this.handle = -1表示当前的ByteBuf不再指向任何一块内存
memory = null这里将memory也设置为null
chunk.arena.free(chunk, handle, maxLength, cache)这一步是将ByteBuf的内存进行释放
recycle()是将对象放入的对象回收站, 循环利用
我们首先分析free方法:
void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) {
//是否为unpooled
if (chunk.unpooled) {
int size = chunk.chunkSize();
destroyChunk(chunk);
activeBytesHuge.add(-size);
deallocationsHuge.increment();
} else {
//那种级别的Size
SizeClass sizeClass = sizeClass(normCapacity);
//加到缓存里
if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) {
return;
}
//将缓存对象标记为未使用
freeChunk(chunk, handle, sizeClass);
}
}
首先判断是不是unpooled, 我们这里是Pooled, 所以会走到else块中:
sizeClass(normCapacity)计算是哪种级别的size, 我们按照tiny级别进行分析
cache.add(this, chunk, handle, normCapacity, sizeClass)是将当前当前ByteBuf进行缓存
我们之前讲过, 再分配ByteBuf时首先在缓存上分配, 而这步, 就是将其缓存的过程, 跟进去:
boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) {
//拿到MemoryRegionCache节点
MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass);
if (cache == null) {
return false;
}
//将chunk, 和handle封装成实体加到queue里面
return cache.add(chunk, handle);
}
首先根据根据类型拿到相关类型缓存节点, 这里会根据不同的内存规格去找不同的对象, 我们简单回顾一下, 每个缓存对象都包含一个queue, queue中每个节点是entry, 每一个entry中包含一个chunk和handle, 可以指向唯一的连续的内存
我们跟到cache中:
private MemoryRegionCache<?> cache(PoolArena<?> area, int normCapacity, SizeClass sizeClass) {
switch (sizeClass) {
case Normal:
return cacheForNormal(area, normCapacity);
case Small:
return cacheForSmall(area, normCapacity);
case Tiny:
return cacheForTiny(area, normCapacity);
default:
throw new Error();
}
}
假设我们是tiny类型, 这里就会走到cacheForTiny(area, normCapacity)方法中, 跟进去:
private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
int idx = PoolArena.tinyIdx(normCapacity);
if (area.isDirect()) {
return cache(tinySubPageDirectCaches, idx);
}
return cache(tinySubPageHeapCaches, idx);
}
这个方法我们之前剖析过, 就是根据大小找到第几个缓存中的第几个缓存, 拿到下标之后, 通过cache去超相对应的缓存对象:
private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
if (cache == null || idx > cache.length - 1) {
return null;
}
return cache[idx];
}
我们这里看到, 是直接通过下标拿的缓存对象
回到add方法中:
boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) {
//拿到MemoryRegionCache节点
MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass);
if (cache == null) {
return false;
}
//将chunk, 和handle封装成实体加到queue里面
return cache.add(chunk, handle);
}
这里的cache对象调用了一个add方法, 这个方法就是将chunk和handle封装成一个entry加到queue里面
我们跟到add方法中:
public final boolean add(PoolChunk<T> chunk, long handle) {
Entry<T> entry = newEntry(chunk, handle);
boolean queued = queue.offer(entry);
if (!queued) {
entry.recycle();
}
return queued;
}
我们之前介绍过, 从在缓存中分配的时候从queue弹出一个entry, 会放到一个对象池里面, 而这里Entry<T> entry = newEntry(chunk, handle)就是从对象池里去取一个entry对象, 然后将chunk和handle进行赋值
然后通过queue.offer(entry)加到queue中
我们回到free方法中:
void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) {
//是否为unpooled
if (chunk.unpooled) {
int size = chunk.chunkSize();
destroyChunk(chunk);
activeBytesHuge.add(-size);
deallocationsHuge.increment();
} else {
//那种级别的Size
SizeClass sizeClass = sizeClass(normCapacity);
//加到缓存里
if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) {
return;
}
freeChunk(chunk, handle, sizeClass);
}
}
这里加到缓存之后, 如果成功, 就会return, 如果不成功, 就会调用freeChunk(chunk, handle, sizeClass)方法, 这个方法的意义是, 将原先给ByteBuf分配的内存区段标记为未使用
跟进freeChunk简单分析下:
void freeChunk(PoolChunk<T> chunk, long handle, SizeClass sizeClass) {
final boolean destroyChunk;
synchronized (this) {
switch (sizeClass) {
case Normal:
++deallocationsNormal;
break;
case Small:
++deallocationsSmall;
break;
case Tiny:
++deallocationsTiny;
break;
default:
throw new Error();
}
destroyChunk = !chunk.parent.free(chunk, handle);
}
if (destroyChunk) {
destroyChunk(chunk);
}
}
我们再跟到free方法中:
boolean free(PoolChunk<T> chunk, long handle) {
chunk.free(handle);
if (chunk.usage() < minUsage) {
remove(chunk);
return move0(chunk);
}
return true;
}
chunk.free(handle)的意思是通过chunk释放一段连续的内存
再跟到free方法中:
void free(long handle) {
int memoryMapIdx = memoryMapIdx(handle);
int bitmapIdx = bitmapIdx(handle);
if (bitmapIdx != 0) {
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
assert subpage != null && subpage.doNotDestroy;
PoolSubpage<T> head = arena.findSubpagePoolHead(subpage.elemSize);
synchronized (head) {
if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) {
return;
}
}
}
freeBytes += runLength(memoryMapIdx);
setValue(memoryMapIdx, depth(memoryMapIdx));
updateParentsFree(memoryMapIdx);
}
if (bitmapIdx != 0)这 里判断是当前缓冲区分配的级别是Page还是Subpage, 如果是Subpage, 则会找到相关的Subpage将其位图标记为0
如果不是subpage, 这里通过分配内存的反向标记, 将该内存标记为未使用
这段逻辑可以读者自行分析, 如果之前分配相关的知识掌握扎实的话, 这里的逻辑也不是很难
回到PooledByteBuf的deallocate方法中:
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, handle, maxLength, cache);
recycle();
}
}
最后, 通过recycle()将释放的ByteBuf放入对象回收站, 有关对象回收站的知识, 会在以后的章节进行剖析
以上就是内存回收的大概逻辑
Netty源码分析第5章(ByteBuf)---->第9节: ByteBuf回收的更多相关文章
- Netty源码分析第6章(解码器)---->第4节: 分隔符解码器
Netty源码分析第六章: 解码器 第四节: 分隔符解码器 基于分隔符解码器DelimiterBasedFrameDecoder, 是按照指定分隔符进行解码的解码器, 通过分隔符, 可以将二进制流拆分 ...
- Netty源码分析第4章(pipeline)---->第4节: 传播inbound事件
Netty源码分析第四章: pipeline 第四节: 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelR ...
- Netty源码分析第4章(pipeline)---->第5节: 传播outbound事件
Netty源码分析第五章: pipeline 第五节: 传播outBound事件 了解了inbound事件的传播过程, 对于学习outbound事件传输的流程, 也不会太困难 在我们业务代码中, 有可 ...
- Netty源码分析第4章(pipeline)---->第6节: 传播异常事件
Netty源码分析第四章: pipeline 第6节: 传播异常事件 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 首先我们看一个最最简单的异常处理的场景 ...
- Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾
Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...
- Netty源码分析第6章(解码器)---->第1节: ByteToMessageDecoder
Netty源码分析第六章: 解码器 概述: 在我们上一个章节遗留过一个问题, 就是如果Server在读取客户端的数据的时候, 如果一次读取不完整, 就触发channelRead事件, 那么Netty是 ...
- Netty源码分析第6章(解码器)---->第2节: 固定长度解码器
Netty源码分析第六章: 解码器 第二节: 固定长度解码器 上一小节我们了解到, 解码器需要继承ByteToMessageDecoder, 并重写decode方法, 将解析出来的对象放入集合中集合, ...
- Netty源码分析第6章(解码器)---->第3节: 行解码器
Netty源码分析第六章: 解码器 第三节: 行解码器 这一小节了解下行解码器LineBasedFrameDecoder, 行解码器的功能是一个字节流, 以\r\n或者直接以\n结尾进行解码, 也就是 ...
- Netty源码分析第4章(pipeline)---->第1节: pipeline的创建
Netty源码分析第四章: pipeline 概述: pipeline, 顾名思义, 就是管道的意思, 在netty中, 事件在pipeline中传输, 用户可以中断事件, 添加自己的事件处理逻辑, ...
- Netty源码分析第4章(pipeline)---->第2节: handler的添加
Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...
随机推荐
- 【洛谷】【线段树】P1471 方差
[题目背景:] 滚粗了的HansBug在收拾旧数学书,然而他发现了什么奇妙的东西. [题目描述:] 蒟蒻HansBug在一本数学书里面发现了一个神奇的数列,包含N个实数.他想算算这个数列的平均数和方差 ...
- shell基础--字符串和变量的操作
一.统计字符串长度 1.wc –L [root@~_~day4]# echo "hello" | wc -L 5 2.expr length string [root@~_~day ...
- PHP常用功能块_错误和异常处理 — php(32)
一.错误和异常处理 1.1 错误类型和基本的调试方法PHP程序的错误发生一般归属于下列三个领域: 语法错误:语法错误最常见,并且也容易修复.如:代码中遗漏一个分号.这类错误会阻止脚本的执行. 运行时错 ...
- mac下git安装和使用
1.下载git客户端,下载地址为:https://git-scm.com/download/mac 2.打开安装包,可以看到此时的界面为: 我们需要把.pkg的安装包安装到系统当中.我双击了安装包 ...
- Jmeter之八大可执行元件及执行顺序
初步接触Jmeter,对比LoadRunner进行熟悉,╮(╯▽╰)╭.毕竟我对LoadRunner还是比Jmeter熟悉. 1.配置元件 用来提供对静态数据配置的支持.例CSV Data Set c ...
- P1446 [HNOI2008]Cards
题目描述 小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案. 进一步,小春要求染出Sr张红色,Sb张蓝 ...
- HDU 1316 (斐波那契数列,大数相加,大数比较大小)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1316 Recall the definition of the Fibonacci numbers: ...
- 实现Redis Cluster并实现Python链接集群
目录 一.Redis Cluster简单介绍 二.背景 三.环境准备 3.1 主机环境 3.2 主机规划 四.部署Redis 4.1 安装Redis软件 4.2 编辑Redis配置文件 4.3 启动R ...
- iOS url出现特殊字符处理 -- stringByAddingPercentEncodingWithAllowedCharacters
stringByAddingPercentEscapesUsingEncoding(只对 `#%^{}[]|\"<> 加空格共14个字符编码,不包括”&?”等符号), i ...
- ThinkPHP5.1中数据查询使用field方法数组参数起别名时遇到的问题
首先数据库基本查询是没有问题的 <?php namespace app\index\controller; use think\Db; class Demo5 { //1.单条查询 public ...