NETTY4中的BYTEBUF 内存管理
转 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 内存管理的更多相关文章
- netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放
ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义 ...
- iOS中引用计数内存管理机制分析
在 iOS 中引用计数是内存的管理方式,虽然在 iOS5 版本中,已经支持了自动引用计数管理模式,但理解它的运行方式有助于我们了解程序的运行原理,有助于 debug 程序. 操作系统的内存管理分成堆和 ...
- C++中的自定义内存管理
1,问题: 1,new 关键字创建出来的对象位于什么地方? 1,位于堆空间: 2,有没有可能位于其它地方? 1,有: 2,通过一些方式可以使动态创建的对象位于静态存储区: 3,这个存储区在程序结束后释 ...
- 分析linux内核中的slub内存管理算法
1. 分析的linux内核源码版本为4.18.0 2. 与slub相关的内核配置项为CONFIG_SLUB 3. 一切都从一个结构体数组kmalloc_caches开始,它的原型如下: ] __ro_ ...
- Netty4 中的内存管理
在Netty4中引入了新的内存管理机制极大地提升其性能,本文将对该内在管理机制进行剖析. 这里有篇文章讲述了在推特(Twitter)内部 使用Netty的状况以及Netty4所带来的性能收益. 在分析 ...
- OC基础--内存管理中的@property关键字以及其参数
在上一篇博客中整理的内存管理,管理类的代码量会感觉很大,而且如果对象多的话,感觉到代码有点冗余.下面就介绍Xcode中为我们自动生成内存管理代码的关键字@property 例如:在Person这个类中 ...
- 理解 iOS 的内存管理
远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳 ...
- 内存管理内幕mallco及free函数实现
原文:https://www.ibm.com/developerworks/cn/linux/l-memory/ 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一.在很多脚本语言中,您不必担 ...
- iOS阶段学习第21天笔记(ARC内存管理-Copy-代理)
iOS学习(OC语言)知识点整理 一.OC 中的ARC内存管理 1)ARC中释放对象的内存原则:看这个对象有没有强引用指向它 2)strong:强引用,默认情况下的引用都是强引用 3) weak:弱引 ...
随机推荐
- django的get_or_create
转:http://www.nanerbang.com/article/51/ get_or_create会根据条件从数据库里面查找符合条件的记录,如果没有符合条件的记录,则新创建一条记录
- NAT打洞
NAT(Network Address Translation)是一种广域网的接入技术,将私有地址转换为合法的公共IP地址,可以完美的解决IP地址不足问题,而且还能有效避免来自外部网络的攻击,隐藏并保 ...
- Oracle使用总结一
一.修改表名要修改索引以及主键 ALTER TABLE AFA_AUTH_FUNCTION RENAME TO BACK_AFA_AUTH_FUNCTION ----修改表名 alter table ...
- es6字符串的扩展学习笔记
1. 传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中.ES6又提供了三种新方法. includes():返回布尔值,表示是否找到了参数字符串. st ...
- jQuery 实现最简单的form表单提交 Loding 功能
<html> <head><title></title></head> <body> <form name="e ...
- 蓝桥杯 基础练习 BASIC-24 龟兔赛跑预测
基础练习 龟兔赛跑预测 时间限制:1.0s 内存限制:512.0MB 问题描述 话说这个世界上有各种各样的兔子和乌龟,但是研究发现,所有的兔子和乌龟都有一个共同的特点——喜欢赛跑.于是世界上各 ...
- 【转】 Pro Android学习笔记(八二):了解Package(1):包和进程
文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 在之前,我们已经学习了如何签发apk,见P ...
- The lesser known pitfalls of allowing file uploads on your website
These days a lot of websites allow users to upload files, but many don’t know about the unknown pitf ...
- spring与struts有什么区别?
Struts只是一个MVC框架(Framework),用于快速开发Java Web应用.Struts实现的重点在C(Controller),包括ActionServlet/RequestProcess ...
- javascipt——对象的概念——数组
一.Array 特点: 数组的长度是可变的: 数组的索引可以是数字.字符串: 数组的内容可以是任意内容: 可以通过索引获取之前不存在的一个位置,其值为undefined: 1.构造函数: new Ar ...