前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty12# 池化内存框架流程

Netty12# 池化内存框架流程

作者头像
瓜农老梁
发布2021-03-16 17:10:34
7900
发布2021-03-16 17:10:34
举报
文章被收录于专栏:瓜农老梁瓜农老梁

前言

本文简要梳理为什么使用池化内存?Netty使用池化内存从哪些方面提升了效率?梳理了池化内存的核心组件大体含义以及内存分配流程,勾勒池化内存的整体框架。后面文章会详细拆解每个点是如何实现的。

一、使用池化内存

为啥要使用池化内存呢? 主要以下两点:

1.频繁申请释放堆外直接内存耗时严重影响效率

2.减少小而不连续的空闲内存(也就是内存碎片)

Netty中又是如何体现内存池并提升效率的呢?

1.将申请的大块内存划分为不同的尺寸

2.不同尺寸的内存使用不同的分配算法,例如:Netty参考slab内存分配算法和Buddy(伙伴)分配算法

3.将划分的不同尺寸缓存起来,使用的时候先从缓存中获取

4.每个线程绑定了专属逻辑内存区域(PoolArena),减少资源竞争

5.使用对象池减少频繁创建销毁性能损耗(ByteBuf对象池)

6.内存用完后,按照特定算法重新合并到大块内存中,看起来像是内存池

二、内存池核心组件

内存池尺寸划分

Netty内存池划分了四种类型尺寸,Netty以Chunk为单位申请内存。内存池主要指16M(默认)以下的内存,大于16M的内存分配不做缓存。

名称

范围

tiny

0~512Byte,内存分配参考了slab算法

small

512Byte~8KB,内存分配同tiny参考了slab算法

normal

8KB~16M,内存分配参考了

huge

大于16M

内存池核心类

类名

说明

PooledByteBufAllocator

内存池门面类,池化内存分配入口

PoolArena

逻辑上的一块内存区域,管理多个PoolChunk

PoolChunk

连续的内存区域,一个Chunk大小为16M。每个Chunk由Page组成,每个Page大小为8KB;包含nomal类型核心内存分配算法(参考了Buddy(伙伴)分配算法)

PoolSubpage

包含8KB以下tiny和small的核心分配算法(参考了slab分配算法)

PoolThreadCache

每个线程都有独立的PoolThreadCache,缓存了tiny类型、small类型、normal类型;分配时先从缓存中获取

Recycler

轻量级对象缓存池,避免频繁创建和消费性能损耗

ResourceLeakDetector

负责内存泄漏检测

三、内存分配流程

下面通过PooledByteBufAllocator#newDirectBuffer()方法,梳理内存分配的整体流程。

代码语言:javascript
复制
 @Override
 protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
   PoolThreadCache cache = threadCache.get(); // 注解@1
   PoolArena<ByteBuffer> directArena = cache.directArena;

  final ByteBuf buf;
  if (directArena != null) {
   buf = directArena.allocate(cache, initialCapacity, maxCapacity); // 注解@2
  } else {
    buf = PlatformDependent.hasUnsafe() ?
    UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
    new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
  }
  return toLeakAwareBuffer(buf); // 注解@3
}

注解@1 从当前线程中获取PoolThreadCache,也就是每个线程都绑定了PoolThreadCache

注解@2 执行内存分配过程

注解@3 通过ResourceLeakDetector检测内存泄漏

跟踪第二步,查看内存分配过程。

代码语言:javascript
复制
 PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
        PooledByteBuf<T> buf = newByteBuf(maxCapacity); 
        allocate(cache, buf, reqCapacity);
        return buf;
}

PooledByteBuf从RECYCLER中获取(对象池)

代码语言:javascript
复制
static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
        PooledUnsafeDirectByteBuf buf = RECYCLER.get();
        buf.reuse(maxCapacity);
        return buf;
}

下面的内存分配过程,先关注主干代码框架,分配过程整体包含了三个部分:tiny&small、small、huge。

huge:大于16M,直接分配堆外直接内存。

small:先从缓存中分配,缓存没有再从内存池分配(借鉴了buddy伙伴算法)

tiny&small:先从缓存中分配,缓存没有再从内存池分配(借鉴了slab算法)

代码语言:javascript
复制
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
        final int normCapacity = normalizeCapacity(reqCapacity);
        if (isTinyOrSmall(normCapacity)) { // capacity < pageSize(8KB) tiny或者small粒度
            int tableIdx;
            PoolSubpage<T>[] table;
            boolean tiny = isTiny(normCapacity);
            if (tiny) { // < 512 tiny粒度
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { // 缓存分配
                    return;
                }
                tableIdx = tinyIdx(normCapacity);
                table = tinySubpagePools;
            } else { // small 粒度
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                    return;
                }
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }
           
            final PoolSubpage<T> head = table[tableIdx];  // 获取对应的节点

            synchronized (head) {
                final PoolSubpage<T> s = head.next;
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
                    incTinySmallAllocation(tiny);
                    return;
                }
            }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
            }

            incTinySmallAllocation(tiny);
            return;
        }
        if (normCapacity <= chunkSize) { // Normal粒度(8K~16M) 
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { // 尝试先从缓存分配
                return;
            }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
                ++allocationsNormal;
            }
        } else {
            allocateHuge(buf, reqCapacity); // 大于16M的huge内存分配
        }
    }

小结下内存分配的整体过程

1.从RECYCLER对象池中复用PooledByteBuf

2.每个线程绑定了缓存PoolThreadCache

3.内存分配时,先从当前线程绑定的PoolThreadCache缓存分配;缓存没有再内存池分配,不同内存尺寸使用不同的分配算法

4.每个分配的Buffer都会由ResourceLeakDetector检测内存泄漏


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档