【Netty 专栏】深入浅出 Netty 内存管理 PoolArena-程序员宅基地

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 

摘要: 原创出处 https://www.jianshu.com/p/4856bd30dd56 「占小狼」欢迎转载,保留摘要,谢谢!

  • PoolArena


前面分别分析了PoolChunk、PoolSubpage和PoolChunkList,本文主要分析PoolArena。
1、深入浅出Netty内存管理 PoolChunk
2、深入浅出Netty内存管理 PoolSubpage
3、深入浅出Netty内存管理 PoolChunkList

PoolArena

应用层的内存分配主要通过如下实现,但最终还是委托给PoolArena实现。

PooledByteBufAllocator.DEFAULT.directBuffer(128);

由于netty通常应用于高并发系统,不可避免的有多线程进行同时内存分配,可能会极大的影响内存分配的效率,为了缓解线程竞争,可以通过创建多个poolArena细化锁的粒度,提高并发执行的效率。

先看看poolArena的内部结构:

img

poolArena

所有内存分配的size都会经过normalizeCapacity进行处理,当size>=512时,size成倍增长512->1024->2048->4096->8192,而size<512则是从16开始,每次加16字节。

poolArena提供了两种方式进行内存分配:

  1. PoolSubpage用于分配小于8k的内存;

  • tinySubpagePools:用于分配小于512字节的内存,默认长度为32,因为内存分配最小为16,每次增加16,直到512,区间[16,512)一共有32个不同值;

  • smallSubpagePools:用于分配大于等于512字节的内存,默认长度为4;

  • tinySubpagePools和smallSubpagePools中的元素都是默认subpage。

  1. poolChunkList用于分配大于8k的内存;

  • qInit:存储内存利用率0-25%的chunk

  • q000:存储内存利用率1-50%的chunk

  • q025:存储内存利用率25-75%的chunk

  • q050:存储内存利用率50-100%的chunk

  • q075:存储内存利用率75-100%的chunk

  • q100:存储内存利用率100%的chunk

img

poolChunkList

  1. qInit前置节点为自己,且minUsage=Integer.MIN_VALUE,意味着一个初分配的chunk,在最开始的内存分配过程中(内存使用率<25%),即使完全释放也不会被回收,会始终保留在内存中。

  2. q000没有前置节点,当一个chunk进入到q000列表,如果其内存被完全释放,则不再保留在内存中,其分配的内存被完全回收。

接下去看看poolArena如何实现内存的分配,实现如下:

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
     
    final int normCapacity = normalizeCapacity(reqCapacity);
    if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
        int tableIdx;
        PoolSubpage<T>[] table;
        boolean tiny = isTiny(normCapacity);
        if (tiny) { // < 512
            if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
            tableIdx = tinyIdx(normCapacity);
            table = tinySubpagePools;
        } else {
            if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
            tableIdx = smallIdx(normCapacity);
            table = smallSubpagePools;
        }

        final PoolSubpage<T> head = table[tableIdx];

        /**
         * Synchronize on the head. This is needed as { @link PoolChunk#allocateSubpage(int)} and
         * { @link PoolChunk#free(long)} may modify the doubly linked list as well.
         */
        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, handle, reqCapacity);

                if (tiny) {
                    allocationsTiny.increment();
                } else {
                    allocationsSmall.increment();
                }
                return;
            }
        }
        allocateNormal(buf, reqCapacity, normCapacity);
        return;
    }
    if (normCapacity <= chunkSize) {
        if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
            // was able to allocate out of the cache so move on
            return;
        }
        allocateNormal(buf, reqCapacity, normCapacity);
    } else {
        // Huge allocations are never served via the cache so just call allocateHuge
        allocateHuge(buf, reqCapacity);
    }
}

1、默认先尝试从poolThreadCache中分配内存,PoolThreadCache利用ThreadLocal的特性,消除了多线程竞争,提高内存分配效率;首次分配时,poolThreadCache中并没有可用内存进行分配,当上一次分配的内存使用完并释放时,会将其加入到poolThreadCache中,提供该线程下次申请时使用。
2、如果是分配小内存,则尝试从tinySubpagePools或smallSubpagePools中分配内存,如果没有合适subpage,则采用方法allocateNormal分配内存。
3、如果分配一个page以上的内存,直接采用方法allocateNormal分配内存。

allocateNormal实现如下:

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
     
    ++allocationsNormal;
    if (q050.allocate(buf, reqCapacity, normCapacity)
     || q025.allocate(buf, reqCapacity, normCapacity)
     || q000.allocate(buf, reqCapacity, normCapacity)
     || qInit.allocate(buf, reqCapacity, normCapacity)
     || q075.allocate(buf, reqCapacity, normCapacity)
     || q100.allocate(buf, reqCapacity, normCapacity)) {
        return;
    }

    // Add a new chunk.
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    assert handle > 0;
    c.initBuf(buf, handle, reqCapacity);
    qInit.add(c);
}

第一次进行内存分配时,chunkList没有chunk可以分配内存,需通过方法newChunk新建一个chunk进行内存分配,并添加到qInit列表中。如果分配如512字节的小内存,除了创建chunk,还有创建subpage,PoolSubpage在初始化之后,会添加到smallSubpagePools中,其实并不是直接插入到数组,而是添加到head的next节点。下次再有分配512字节的需求时,直接从smallSubpagePools获取对应的subpage进行分配。

img

smallSubpagePools

分配内存时,为什么不从内存使用率较低的q000开始?在chunkList中,我们知道一个chunk随着内存的释放,会往当前chunklist的前一个节点移动。

q000存在的目的是什么?
q000是用来保存内存利用率在1%-50%的chunk,那么这里为什么不包括0%的chunk?
直接弄清楚这些,才好理解为什么不从q000开始分配。q000中的chunk,当内存利用率为0时,就从链表中删除,直接释放物理内存,避免越来越多的chunk导致内存被占满。

想象一个场景,当应用在实际运行过程中,碰到访问高峰,这时需要分配的内存是平时的好几倍,当然也需要创建好几倍的chunk,如果先从q0000开始,这些在高峰期创建的chunk被回收的概率会大大降低,延缓了内存的回收进度,造成内存使用的浪费。

那么为什么选择从q050开始?
1、q050保存的是内存利用率50%~100%的chunk,这应该是个折中的选择!这样大部分情况下,chunk的利用率都会保持在一个较高水平,提高整个应用的内存利用率;
2、qinit的chunk利用率低,但不会被回收;
3、q075和q100由于内存利用率太高,导致内存分配的成功率大大降低,因此放到最后;



如果你对 Dubbo 感兴趣,欢迎加入我的知识星球一起交流。


知识星球

目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:

01. 调试环境搭建
02. 项目结构一览
03. 配置 Configuration
04. 核心流程一览

05. 拓展机制 SPI

06. 线程池

07. 服务暴露 Export

08. 服务引用 Refer

09. 注册中心 Registry

10. 动态编译 Compile

11. 动态代理 Proxy

12. 服务调用 Invoke

13. 调用特性 

14. 过滤器 Filter

15. NIO 服务器

16. P2P 服务器

17. HTTP 服务器

18. 序列化 Serialization

19. 集群容错 Cluster

20. 优雅停机

21. 日志适配

22. 状态检查

23. 监控中心 Monitor

24. 管理中心 Admin

25. 运维命令 QOS

26. 链路追踪 Tracing

...
一共 60 篇++

源码不易↓↓↓

点赞支持老艿艿↓↓

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/github_38592071/article/details/103370838

智能推荐

LeetCode_每日一题今日份_312.戳气球(没懂)-程序员宅基地

文章浏览阅读137次。题解Javaclass Solution { public int[][] rec; public int[] val; public int maxCoins(int[] nums) { int n = nums.length; val = new int[n + 2]; for (int i = 1; i <= n; i++) { val[i] = nums[i - 1]; }.

初识activiti-程序员宅基地

文章浏览阅读60次。Activity工作流学习要点1. 1个插件在Eclipse中安装Activity插件,让你可以在Eclipse中绘制Activity工作流图2. 1个引擎ProcessEngine对象,Activity工作流引擎。这是Activiti工作的核心。负责生成流程运行时的各种实例及数据、监控和管理流程的运行。所有的操作都是从获取引擎开始的,所以一般会把引擎作为全局变量Proc..._activiti proc_def_id_ 由组成

3D建模教程:3DMAX打造下雨的场景!_造雨系统3d软件-程序员宅基地

文章浏览阅读1.7k次。1、 创建——PF粒子系统当然在学习学习3Dmax,zbrush maya建模,次世代美术的道路上肯定会困难,没有好的学习资料,怎么去学习呢?如果你感觉学不会?莫慌,小编推荐大家加入群,群号684663881里有志同道合的小伙伴,互帮互助,还可以拿到许多视频教程! 2、粒子视图——修改粒子参数 ..._造雨系统3d软件

Jupyter notebook安装与使用_如何将jupyter环境与conda环境进行匹配-程序员宅基地

文章浏览阅读778次。Jupyter notebook安装与使用_如何将jupyter环境与conda环境进行匹配

JavaScript-JS判断一个整数是偶数还是奇数_let num =prompt("请输入一个整登录 if (num %2 === 0){ conso-程序员宅基地

文章浏览阅读3.3k次,点赞2次,收藏4次。let num = prompt("请输入一个整数");if (num % 2 === 0){ console.log("这个数是偶数");} else if (num % 2 === 1){ console.log("这个数是奇数");}_let num =prompt("请输入一个整登录 if (num %2 === 0){ console.log("这个数

Google VR开发-Cardboard VR SDK反畸变实现_distortion_cardboard-程序员宅基地

文章浏览阅读2.9k次。上一篇文章分析了Cardboard SDK的生命周期设计。这里我们看下畸变部分的实现。Cardboard中将畸变这部分封装成了一个Distortion类和DistortionRenderer类。我们看下Distortion这个类: private static final float[] DEFAULT_COEFFICIENTS = { 250.0F, 50000.0_distortion_cardboard

随便推点

VMware中如何实现Linux系统与宿主机文件共享_vmwarelinux与宿主机共享内存-程序员宅基地

文章浏览阅读820次。参考:http://hi.baidu.com/fly_2009hui/blog/item/a62b484f0f4ac63baec3ab73.html使用hgfs实现vmare文件传输一法使用vmware(vmware workstation 5)下shared folders功能实现vmware中host与ghost间文件传输,无需任何网络相关设置,不使用任何网络协议,host和gho_vmwarelinux与宿主机共享内存

go 源码篇(三)CSP GMP Channel_golang 开源csp-程序员宅基地

文章浏览阅读576次。1goroutine原理1.1基本概念并发:一个CPU上能同时执行多项任务,在很短时间内,CPU来回切换任务执行(在某段很短时间内执行程序a,然后又迅速得切换到程序b去执行),有时间上的重叠(宏观上是同时的,微观仍是顺序执 行),这样看起来多个任务像是同时执行,这就是并发。并行当系统有多个CPU时,每个CPU同一时刻都运行任务,互不抢占自己所在的CPU资源,同时进行, 称为并行。进程CPU在切换程序的时候,如果不保存上一个程序的状态(context–上下文),直接切换下一个程 序,就会丢失_golang 开源csp

高一计算机课期中考试总结反思,2017高一数学期中考试反思总结-程序员宅基地

文章浏览阅读76次。引导语:数学新课改的基本理念是:学有价值的数学,反映出学生实践能力和创新意识方面的不足,应引起我们的高度重视,学生的动手能力还有待提高。以下是百分网小编分享给大家的2017高一数学期中考试反思总结,欢迎阅读!过去的一学期里,我班在学校领导的统一组织下,在任课教师的大力支持和配合下,各项工作顺利开展,学习、生活等方面都取得较突出的成绩。现将本学期期中考试前的工作总结如下:一、 加强对学生的思想政治工...

如何使用Stripe和Syncano建立每日确认短信服务-程序员宅基地

文章浏览阅读276次。这篇文章是由赞助Syncano 。 感谢您支持谁使SitePoint可能的赞助商。 Syncano提供了实时应用的基于云的平台。 它存储数据,微服务代码,日程安排用于自动执行代码,用户帐户,网络挂接通过HTTP多以访问这些功能。 他们甚至已经得到的代码片段的开源社区,并支持多种运行环境,包括节点,巨蟒,围棋和Ruby。 从一个开发者角度,Syncano可以更容易获得通过提供大量的,否则你就需..._如何用stripe

MQ和ActiveMQ浅析_activemq和ibmmq的区别-程序员宅基地

文章浏览阅读844次。文章目录什么是JMS MQ消息中间件应用场景**异步通信**缓冲解耦冗余扩展性可恢复性顺序保证**过载保护****数据流处理**常用消息队列(ActiveMQ、RabbitMQ、RocketMQ、Kafka)比较JMS中的一些角色**Broker**providerConsumerp2ppub/subPTP 和 PUB/SUB 简单对QueueTopicConnectionFactoryConnectionDestinationSessionJMS的消息格式JMS消息由以下三部分组成的:TextMessag_activemq和ibmmq的区别

造梦师手记:SDXL更新最勤奋的梦幻模型_dreamshaper模型和sdxl模型的区别-程序员宅基地

文章浏览阅读238次。天道酬勤,这个模型也成为C站下载量最大的SDXL模型之一(当然,距离dreamshaperXL还有不小的差距)。但是,随着拥护者的增多,越来越多的建议得到反馈,就有望成为SDXL时代的顶尖少数几个大模型之一。可以预见的未来,至少在SDXL时代,由于SDXL本身的强大数据集,模型也会出现“马太效应”,会向极少数模型聚集。当然了,如果你能习惯comfyUI,也是非常不错的,这个UI虽然对很多新手界面不太友好,但对系统资源占用较少。短短不到一个月的时间,涌现出大量的模型和LoRA,丰富了我们的创作素材。_dreamshaper模型和sdxl模型的区别

推荐文章

热门文章

相关标签