Netty篇:ByteBuf之堆外内存与回收策略源码分析(非池化)


         上文描述过,ByteBuf根据内存分可以分为堆内存和堆外内存实现,此处以非池化实现介绍,两个重要子类是UnpooledHeapByteBuf和UnpooledDirectByteBuf,因为堆外内存和堆内存除了内存分配之外,其他实现十分相似,先简单描述下堆内存实现,然后重点分析堆外内存的分配及回收策略


堆内存实现


      ByteBuf的堆内存主要由子类UnpooledHeapByteBuf实现,UnpooledHeapByteBuf是基于堆内存进行内存分配的字节缓冲区,它没有基于对象池技术实现,每次I/O的读写都会创建一个新的UnpooledHeapByteBuf。


      基础属性如下:


在这里插入图片描述

      聚合了一个ByteBufAllocator用于UnpooledHeapByteBuf的内存分配,一个byte数组作为缓冲区,最后定义了一个ByteBuffer类型的tmpNioBuf变量用于实现Netty ByteBuf到JDK NIO ByteBUffer的转换。



      在父接口基础上实现了扩容接口:

在这里插入图片描述

      首先对新容量进行合法性校验,如果大于容量上限或者小于0,则抛出异常。

      判断新的容量是否大于当前的缓冲区容量,大于则需要进行动态扩展。创建新的缓冲区字节数组,然后System.arraycopy进行内存复制,setArray替换旧的字节数组,释放旧内存。

在这里插入图片描述

      将ByteBuf转换为ByteBuffer,使用了NIO的ByteBuffer提供的wrap方法,将byte数组转换为ByteBuffer对象。




堆外内存及回收策略


      入口在UnpooledByteBufAllocator#newDirectBuffer方法。Netty提供了两种内存策略,一种是使用java.nio.DirectByteBuffer实现,另一种是noCleaner策略实现。用不同的对象实现,均继承自UnpooledUnsafeDirectByteBuf。

在这里插入图片描述


DirectByteBuffer实现


      实现类为InstrumentedUnpooledUnsafeDirectByteBuf,继承关系如下图:

在这里插入图片描述

重写了内存分配和回收的方法:

在这里插入图片描述

分配内存时,直接调用java.nio.DirectByteBuffer实现。


在这里插入图片描述

DIrectByteBuffer的内存分配代码如下:

在这里插入图片描述

      向Bits类申请内存,Bits类有一个全局的 totalCapacity变量,记录着全部DirectByteBuffer的总大小,每次申请,都先看看是否超限,堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。

跟踪reserveMemory:

在这里插入图片描述

      如果已经超限,会主动执行Sytem.gc(),试图回收DirectByteBuffer,并释放部分堆外内存。然后休眠一百毫秒,如果内存还是不足,就抛出OOM异常。

      如果内存足够,则调用Unsafe分配堆外内存返回内存基地址。

      最后,创建一个Cleaner,并把代表清理动作的Deallocator类绑定,降低Bits里的totalCapacity,并调用Unsafe调free去释放内存。

      Cleaner类继承了PhantomReference虚引用类,是一个虚引用对象(不影响对象的生命周期),GC时发现它除了PhantomReference外已不可达(持有它的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。而PhantomReference继承自Reference。Reference在静态块中启动了一个handler守护线程,死循环处理tryHandlerPending方法,主要逻辑是判断不可达对象是否为Cleaner,如果是,则调用其clean方法回收堆外内存。以此将堆外内存与GC相关联。


Reference静态块代码如下:


在这里插入图片描述

tryHandlerPending代码如下:

在这里插入图片描述


      存在于堆内的DirectByteBuffer对象很小,只存着基地址和大小等几个属性,和一个Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。如果熬过了young gc,进入老生带之后,因为很小很难触发full gc,如果没有别的大块头进入老生代触发full gc,就会占着一大片堆外内存不释放。虽然申请内存并且不足时会调用Systen.gc(),但是他中断整个进程,然后它让当前线程睡了整整一百毫秒,而且如果gc没在一百毫秒内完成,它仍然会抛出OOM异常。如果设置了-DisableExplicitGC禁止了system.gc(),就更难受了。所以,堆外内存还是调用Cleaner的clean方法主动回收比较好。


      Netty也提供了主动回收的release方法。


在这里插入图片描述

      此处用到了引用计数器,如果是2(引用计数为偶数,详情见博客-10),说明是最后的引用,尝试回收空间,并将其归零,如果不为2则修改其值,并判断是否需要回收内存空间。如果需要则调用deallocate()方法回收内存。

在这里插入图片描述


      经过一段比较长的调用,最终会调用CleanerJava6中的freeDirectBuffer0,通过反射获取cleaner对象并执行其clean方法。另外还有CleanerJava9的实现,使用Unsafe的invokeCleaner方法。

在这里插入图片描述




noCleaner策略


      实现类为InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf,继承关系如下图:

在这里插入图片描述

重写了分配内存,重新分配内寸和释放内存方法:

在这里插入图片描述

分配内存时,调用PlatformDependent#allocateDIrectNoCleaner实现:


在这里插入图片描述

      最终实现是使用Unsafe分配内存后返回内存对象地址,并以此为参数反射调用DirectByteBuffer(long addr,int cap)构造方法创建实例。

在这里插入图片描述

      DirectByteBuffer(long addr,int cap)构造方法并不会申请内存,也不会创建Cleaner对象,只是做了基本参数的赋值。申请内存操作已经在allocateDirectNoCleaner中已经做过了。


在这里插入图片描述

      noClean的release方法实现基本相同,只是在释放内寸的时候,实现不一样,直接用unsafe来释放内存。

在这里插入图片描述

在这里插入图片描述




©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354