转 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. 要做Java程序员 需要知道那些技术 重点有那些

    一.Java程序员         高级特性               反射.泛型.注释符.自动装箱和拆箱.枚举类.可变               参数.可变返回类型.增强循环.静态导入     ...

  2. RabbitMQ和Kafka可靠性

    RabbitMQ和Kafka可靠性 https://www.cnblogs.com/haolujun/p/9641840.html 我们通过前文知道,RabbitMQ的队列分为master queue ...

  3. deque容器

    一.deque容器基本概念 deque是“double-ended queue”的缩写,和vector一样,deque也支持随机存取.vector是单向开口的连续性空间,deque则是一种双向开口的连 ...

  4. Vue 内联样式的数据绑定

    Vue 内联样式的数据绑定 之前学的是数据绑定 class,现在可以将数据绑定到 style 中. <div id="app"> <div v-bind:styl ...

  5. Salesforce注册教程

    打开网址 https://developer.salesforce.com/,点击右上角的Sign up(注册) 在页面输入基本信息 >Name:First(姓名) Last(姓)  >E ...

  6. win32 获取本机网卡信息(MAC地址,IP地址等)

    由于一个需求需要获取网卡的MAC地址,就搜了一下,大部分都是COPY来COPY去的一些代码,有很多甚至不能直接运行或有还有内存泄漏.自己查了一下MSDN然后封装了一下: 需要注意,一个机器可能有多个网 ...

  7. Azure VM的加速网络

    Azure的VM在经过几代发展后已经有多种硬件类型.目前Azure China的多种机型都配置了FPGA卡,可以实现网络加速.本文将介绍Azure的加速网络相关的内容. 一. 加速网络的硬件准备 下图 ...

  8. Angular5学习笔记 - 集成Bootstrap、Jquery、Tether(三)

    一.添加配置 cnpm i bootstrap jquery tether --save 添加后效果 二.配置添加样式和js的引用 打开.angular-cli.json文件,在styles和scri ...

  9. 蓝桥杯 基础练习 BASIC-22 FJ的字符串

    基础练习 FJ的字符串   时间限制:1.0s   内存限制:512.0MB 问题描述 FJ在沙盘上写了这样一些字符串: A1 = “A” A2 = “ABA” A3 = “ABACABA” A4 = ...

  10. ORACLE显式授权

    同一数据库 两个不同用户 user1 user2 user1里面有一张表 table1 在user2里面创建synonymcreate synonym sy1 for user1.table1; 创建 ...