http://hushi55.github.io/2014/12/31/netty-memory-manage
C/C++ 和 java 中有个围城,城里的想出来,城外的想进去!
这个围城就是自动内存管理!
Netty4 带来一个与众不同的特点是其 ByteBuf 的实现,相比之下,通过维护两个独立的读写指针,
要比 io.netty.buffer.ByteBuf 简单不少,也会更高效一些。不过,Netty 的 ByteBuf
带给我们的最大不同,就是他不再基于传统 JVM 的 GC 模式,相反,它采用了类似于 C++ 中的 malloc/free
的机制,需要开发人员来手动管理回收与释放。从手动内存管理上升到GC,是一个历史的巨大进步,
不过,在20年后,居然有曲线的回归到了手动内存管理模式,正印证了马克思哲学观:
社会总是在螺旋式前进的,没有永远的最好。
的确,就内存管理而言,GC带给我们的价值是不言而喻的,不仅大大的降低了程序员的心智包袱,
而且,也极大的减少了内存管理带来的Crash困扰,为函数式编程(大量的临时对象)、脚本语言编程带来了春天。
并且,高效的GC算法也让大部分情况下程序可以有更高的执行效率。
不过,也有很多的情况,可能是手工内存管理更为合适的。譬如:
所以,理论上,尴尬的GC实际上比较适合于处理介于这2者之间的情况:
对象分配的频繁程度相比数据处理的时间要少得多的,但又是相对短暂的,
典型的,对于OLTP型的服务,处理能力在1K QPS量级,每个请求的对象分配在10K-50K量级,
能够在5-10s的时间内进行一次younger GC,每次GC的时间可以控制在10ms水平上,
这类的应用,实在是太适合GC行的模式了,而且结合Java高效的分代GC,简直就是一个理想搭配。
Netty 4 引入了手工内存的模式,我觉得这是一大创新,这种模式甚至于会延展,
应用到Cache应用中。实际上,结合JVM的诸多优秀特性,如果用Java来实现一个Redis型Cache、
或者 In-memory SQL Engine,或者是一个Mongo DB,我觉得相比C/C++而言,都要更简单很多。
实际上,JVM也已经提供了打通这种技术的机制,就是Direct Memory和Unsafe对象。
基于这个基础,我们可以像C语言一样直接操作内存。实际上,Netty4的ByteBuf也是基于这个基础的。
由于facebook而火起来的 jemalloc 广为人之,但殊不知,它在malloc界里面很早就出名了。
jemalloc 的创始人Jason Evans也是在FreeBSD很有名的开发人员。此人就在2006年为提高低性能的
malloc而写的 jemalloc。 jemalloc是从2007年开始以FreeBSD标准引进来的。
软件技术革新很多是FreeBSD发起的。在FreeBSD应用广泛的技术会慢慢导入到linux。
jemalloc的整体结构
jemalloc 中的分块管理
chunk 将是用一个
以下源码分析是基于 netty 4.0.24.Final, 首先来看张 netty 内存的整体图
从中可以看出 netty 是将内存分为 arena, chunklist, chunk, subpage, 其中 subpage 又分为 tiny subpage pools 和 small subpage pools, 这些逻辑分类中以 chunk 为中心每个chunk 的默认大小是 16M, chunk 的管理如下图
chunk 使用一个完全二叉树来管理,数组的 0 index 没有使用,depth 代表树的深度
树的搜索算法,当申请内存是从树的根节点开始,
/** * Algorithm to allocate an index in memoryMap when we query for a free node * at depth d * * @param d depth * @return index in memoryMap */ private int allocateNode(int d) { int id = 1; int initial = - (1 << d); // has last d bits = 0 and rest all = 1 byte val = value(id); if (val > d) { // unusable return -1; } while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0 id <<= 1; val = value(id); if (val > d) { id ^= 1; val = value(id); } } byte value = value(id); assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d", value, id & initial, d); setValue(id, unusable); // mark as unusable updateParentsAlloc(id); return id; }
若是申请的内存小于 pageSize,netty 将一个 page 分割为多个 subpage 来管理,每个 subpage 的大小是一样的。
我们知道 JVM 的是基于垃圾回收机制的,并且垃圾回收是基于图的搜索算法。但是 Netty 没有依拉 JVM 的垃圾回收机制,对内存的管理使用的 引用计数 的方法。