转 http://iteches.com/archives/65193

Netty4带来一个与众不同的特点是其ByteBuf的重现实现,老实说,java.nio.ByteBuf是我用得很不爽的一个API,相比之下,通过维护两个独立的读写指针,io.netty.buffer.ByteBuf要简单不少,也会更高效一些。不过,Netty的ByteBuf带给我们的最大不同,就是他不再基于传统JVM的GC模式,相反,它采用了类似于C++中的malloc/free的机制,需要开发人员来手动管理回收与释放。从手动内存管理上升到GC,是一个历史的巨大进步,不过,在20年后,居然有曲线的回归到了手动内存管理模式,正印证了马克思哲学观:社会总是在螺旋式前进的,没有永远的最好。

的确,就内存管理而言,GC带给我们的价值是不言而喻的,不仅大大的降低了程序员的心智包袱,而且,也极大的减少了内存管理带来的Crash困扰,为函数式编程(大量的临时对象)、脚本语言编程带来了春天。而且,高效的GC算法,也让大部分情况下,程序可以有更高的执行效率。不过,也有很多的情况,可能是手工内存管理更为合适的。譬如:

1、大量的长期生存的对象。如Cache管理等。GC在这里只是步履艰难的做着低效的清理工作。在Java中,一直没有什么很成熟的Cache方案,就跟这个是有莫大关系的。

2、高吞吐量下的过于频繁的对象分配。在这种模式下,单个服务处理的时间片其实很短,但却产生了很大的对象分配。虽然这里的对象也是具有很短暂的生命周期,但过于频繁的分配导致GC也变得频繁。即使是一次Younger GC,其成本也远大于一次简单的服务处理。

所以,理论上,尴尬的GC实际上比较适合于处理介于这2者之间的情况:对象分配的频繁程度相比数据处理的时间要少得多的,但又是相对短暂的,典型的,对于OLTP型的服务,处理能力在1K QPS量级,每个请求的对象分配在10K-50K量级,能够在5-10s的时间内进行一次younger GC,每次GC的时间可以控制在10ms水平上,这类的应用,实在是太适合GC行的模式了:而且结合Java高效的分代GC,简直就是一个理想搭配。

但是,对于类似于业务逻辑相对简单,譬如网络路由转发型应用(很多erlang应用其实是这种类型),QPS非常高,比如1M级,在这种情况下,在每次处理中即便产生1K的垃圾,都会导致频繁的GC产生。在这种模式下,或者是erlang的按进程回收模式,或者是C/C++的手工回收机制,效率更高。

至于Cache型应用,由于对象的存在周期太长,GC基本上就变得没有价值。

Netty 4 引入了手工内存的模式,我觉得这是一大创新,这种模式甚至于会延展,应用到Cache应用中。实际上,结合JVM的诸多优秀特性,如果用Java来实现一个Redis型Cache、或者 In-memory SQL Engine,或者是一个Mongo DB,我觉得相比C/C++而言,都要更简单很多。实际上,JVM也已经提供了打通这种技术的机制,就是Direct Memory和Unsafe对象。基于这个基础,我们可以像C语言一样直接操作内存。实际上,Netty4的ByteBuf也是基于这个基础的。

本文简单的分析一下Netty 4中是如何管理内存的分配的。

Netty 4 introduces a high-performance buffer pool which is a variant of jemalloc that combines buddy allocation and slab allocation.

根据官网的介绍,我查看了buddy allocation及slab allocation的基本算法,再结合Netty的源代码,大致整理了数据结构如下

PoolChunk:一大块连续的内存,Netty中,这个值为16M,一次性通过 java.nio.ByteBuf 进行分配,这个内存是Direct Memory,不在JVM的GC范围之内。多个PoolChunk可以共同构成一个PoolArea。每个PoolChunk按照Buddy算法分为多个block,最小的block:order-0 block(称之为一个PoolSubPage)是8K,而后是order-1 block:16K, order-2 block:32 K,一直到 order-11 block: 16M

在PoolChunk中,使用一个int[4096] memoryMap来描述所有的block,这其实是一个二叉树来的:

0

1                2

3       4       5       6

07 08 09 10 11 12 13 14

这里, memoryMap[1] 表示的是order-11的Block,它实际上可以切分为两个order-10的block,他们由 memoryMap[2]和 memoryMap[3]来表示,每个值由3部分组成:

31     17 16         2 1 0

0-1位:标志位,00(未使用,没有这个Block)、01(Branch,当前Block以拆分,由2个低级别的block组成)、02(已分配)、03(已分配的SubPage,这个是最底层分配的Block了,可以是大于8K的Page。)

2-16位:当前块的大小,以Page为单位。

17 – 31位:当前块相对Chunk基地址的偏移量。(以Page为单元)

使用数组来替代二叉树的优势是极大的节约了内存,第N的节点的子节点是2N+1, 2N+2。在这里,16M的Chunk需要使用16K来描述,占比为0.1%

PoolSubPage对应于一个已分配的block,在Slab实现中,每个PoolSubPage都仅用于某个一定size的内存分配,例如,这个值从16、32到512(TinyPool)是按16递增,而后是1K、2K、4K、8K、16K、32K、64K…(smallPool)的递增顺序,每个SubPage都近用于分配固定大小的内存(称之为一个Slab),这样的优势是只需要使用一个bitmap就可以记录哪些内存是已分配的,以最小的16字节为单位,每个Page(8K)只需要64个字节的位图信息(占比为0.78%),而在其它块中,这个值就更小了。(目前Netty的实现在这里有一些不足,每个Page都是用了64个字节的位图,估计是便于简化SubPage自身的Pool)

每个Chunk按照Slab的大小,组织了多条队列,队列中的每个成员是SubPage,因此,当需要进行分配时,是可以最快速的完成的。

每个ByteBuf()维持了对应的Chunk,当需要释放时,可以根据当前的内存地址,迅速的定位到Chunk中对应的page,再在相应的SubPage中进行释放。整个过程只需要更新相应的bitmap即可。

忽略掉其它的内存开销,Slab+Buddy方式的内存管理成本不到1%,分配和释放速度都非常快。但是Slab的方式,整体内存的使用率方面可能会小一些,不同Slab之间的内存是不会共享的,相当于给大猫挖一个大的猫洞的情况下,也得给小猫挖一个小的猫洞。

=========================

总的来说,Netty的ByteBuf带给我们的启示就是,即便在JVM中,我们也不必拘泥于GC的内存管理方式,在更适合使用手工方式管理的情况下,我们也可以这样做,这也为Java管理海量的内存、Cache化的数据、以及高频繁分配的模式下,仍然可以借助于JVM的强大的能力,而又不是去直接管理内存的灵活性

NETTY4中的BYTEBUF 内存管理的更多相关文章

  1. netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

    ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义 ...

  2. iOS中引用计数内存管理机制分析

    在 iOS 中引用计数是内存的管理方式,虽然在 iOS5 版本中,已经支持了自动引用计数管理模式,但理解它的运行方式有助于我们了解程序的运行原理,有助于 debug 程序. 操作系统的内存管理分成堆和 ...

  3. C++中的自定义内存管理

    1,问题: 1,new 关键字创建出来的对象位于什么地方? 1,位于堆空间: 2,有没有可能位于其它地方? 1,有: 2,通过一些方式可以使动态创建的对象位于静态存储区: 3,这个存储区在程序结束后释 ...

  4. 分析linux内核中的slub内存管理算法

    1. 分析的linux内核源码版本为4.18.0 2. 与slub相关的内核配置项为CONFIG_SLUB 3. 一切都从一个结构体数组kmalloc_caches开始,它的原型如下: ] __ro_ ...

  5. Netty4 中的内存管理

    在Netty4中引入了新的内存管理机制极大地提升其性能,本文将对该内在管理机制进行剖析. 这里有篇文章讲述了在推特(Twitter)内部 使用Netty的状况以及Netty4所带来的性能收益. 在分析 ...

  6. OC基础--内存管理中的@property关键字以及其参数

    在上一篇博客中整理的内存管理,管理类的代码量会感觉很大,而且如果对象多的话,感觉到代码有点冗余.下面就介绍Xcode中为我们自动生成内存管理代码的关键字@property 例如:在Person这个类中 ...

  7. 理解 iOS 的内存管理

    远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳 ...

  8. 内存管理内幕mallco及free函数实现

    原文:https://www.ibm.com/developerworks/cn/linux/l-memory/ 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一.在很多脚本语言中,您不必担 ...

  9. iOS阶段学习第21天笔记(ARC内存管理-Copy-代理)

    iOS学习(OC语言)知识点整理 一.OC 中的ARC内存管理 1)ARC中释放对象的内存原则:看这个对象有没有强引用指向它 2)strong:强引用,默认情况下的引用都是强引用 3) weak:弱引 ...

随机推荐

  1. codewar代码练习2——7级晋升6级

    7级晋升到6级的过程中以做6级题以及以前未完成的题目为主,一般选择算法题或者基础题.相比之前从8级升级7级(参见此博客:http://blog.csdn.net/m0_37324740/article ...

  2. 使用jsonp跨域调用百度js实现搜索框智能提示(转)

    http://www.cnblogs.com/oppoic/p/baidu_auto_complete.html 项目中常常用到搜索,特别是导航类的网站.自己做关键字搜索不太现实,直接调用百度的是最好 ...

  3. ZOJ2112 Dynamic Rankings (线段树套平衡树)(主席树)

    The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with t ...

  4. BZOJ4923 [Lydsy1706月赛]K小值查询

    题意 维护一个长度为n的正整数序列a_1,a_2,...,a_n,支持以下两种操作: 1 k,将序列a从小到大排序,输出a_k的值. 2 k,将所有严格大于k的数a_i减去k. \(n \leq 10 ...

  5. webpack里CommonJS的require与ES6 的module.exports加载模块有何不同

    只需明白commonjs的规则即可,import会被转化为commonjs格式的,babel默认会把ES6的模块转化为commonjs规范的. import vue from 'vue'; //等价于 ...

  6. 一个Bug 差点让服务器的文件系统崩溃

    昨天,公司的美国客户发邮件给我,说我的软件出问题了,我查来查去,发现居然是服务器上一个目录无法删除,一删除就报 cannot read from the source file or disk. 如果 ...

  7. CentOS6.5 下MySQL傻瓜式安装

    为了为服务器上装mysql我先在虚拟机上练习了一下特此记录并分享; 注:参考文章https://www.cnblogs.com/xiaoluo501395377/archive/2013/04/07/ ...

  8. BZOJ4170:极光

    浅谈离线分治算法:https://www.cnblogs.com/AKMer/p/10415556.html 题目传送门:https://lydsy.com/JudgeOnline/problem.p ...

  9. linux find -regex 使用正则表达式

    find之强大毋庸置疑,此处只是带领大家一窥find门径,更详细的说明见man  find和 info find.整篇文章循序渐进,从最常用的文件名测试项开始步步深入,到第六节基本讲完find处理文件 ...

  10. Erlang pool management -- Emysql pool optimize

    在上一篇关于Emysql pool (http://www.cnblogs.com/--00/p/4281938.html)的分析的最后提到 现在的emysql_conn_mgr gen_server ...