Ruby 内存分配

Ruby内存结构图.png

Ruby会在堆空间中维护一个对象池,这个对象池也被称作ObjectSpace, 我们所使用的所有Ruby对象全部是从这个池子中取出的,而Ruby GC也会去清理池子中已经没有被使用的对象,达到循环利用的目的。

ObjectSpace对象池是由很多堆页(heap page)构成的,而每一个heap page页的大小为16Kb。 每页中包含408个槽(slot),因此每个slot的大小为40 bytes。 每个slot槽对应一个对象,你所使用的每一个对象都在对象池中占有一个槽。这些数据可以通过下面的方式得到:

GC::INTERNAL_CONSTANTS
=> {:RVALUE_SIZE=>40, :HEAP_PAGE_OBJ_LIMIT=>408, :HEAP_PAGE_BITMAP_SIZE=>56, :HEAP_PAGE_BITMAP_PLANES=>4}
 # 一个RVALUE结构体40个字节:HEAP_PAGE_OBJ_LIMIT=>408

Ruby有一种称为RValue的结构,对象被存放在这个结构体中,除了对象之外,还附有一个flag,也就是在mark-and-sweep机制被mark时要使用的flag。

在上图的栈空间中存放的是 当前运行栈的RVALUE指针和值,而堆空间存放的是RVALUE的值,按照Ruby2.1以下版本默认初始化10000个slots 计算,结果就是堆空间默认包含24个heap page。

系统堆空间存放的是数据,即RVALUE中无法存放的的数据,比如一个1K的字符串,RVALUE只能存放引用和元信息和23个字符,剩下真正的数据是存放在系统管理的堆空间的,数据用完后会被GC释放。

require 'objspace'
ObjectSpace.memsize_of('a')  # 0 or 40?
ObjectSpace.memsize_of('a'*23) # 0 or 40 or more ?
ObjectSpace.memsize_of('a'*24) # 0 or 40 or more ?

不会被回收的内存

Ruby 中的常量是永远不会被垃圾回收的,所以如果常量引用了一个对象,那么这个对象也永远不会被垃圾回收,是由于将来 可能 会使用它们。在 Ruby 中一个对象一旦被全局对象引用,它就不会被垃圾回收。 这一原则也适用于常量,全局变量,模块 (modules) 和类 (class)。因此,在全局可访问的任何地方引用对象都要注意这一点。
ruby 的程序会释放内存给操作系统,但是如果一个 heap 里存在不会被垃圾回收的对象,那这个 heap 的空间就不会释放给操作系统,同时内存释放很慢,容易发生内存持续上涨的情况。

RubyGC方式

  • Ruby 1.8:简单的标记和扫描
    ruby中的GC使用的是 mark and sweep机制,ruby会从根目录扫描全部对象,如果能 reach到这个对象,就使用名为FL_MARK的内部标志之一标记变量,扫描完毕没有被 mark的对象就会被回收。扫描过程是处于 Stop The word 暂停状态。
image
  • Ruby 1.9.3:懒惰扫描
    标记完成后,不一次性清除所有无用的对象,将清除的过程分散到后面的操作中,这大大降低了单次清除过程中的中断时间。

  • Ruby 2.0中的垃圾收集:位图标记
    不是在每个RValue结构中使用FL_MARK位来指示Ruby仍在使用值并且无法释放它,而是将Ruby 2.0中的信息保存在称为“位图”的内容中(位图指映射回RValue结构的文字位集合)

image

1值等同于在Ruby 1.8或Ruby 1.9进程中设置的FL_MARK标志,而0等效于未设置的FL_MARK标志。换句话说,FL_MARK位已经移出RString和其他对象值结构,并进入称为位图的这个单独的存储区域,允许Unix系统的 Copy -On- Write 跨子进程共享内存。

  • Ruby 2.1:分代GC
    oldgen和minor标记 (一个可以逐步实现并支持C扩展的分代收集器)

  • Ruby2.2:引入增量标记和symbol GC
    主标记拆分为数个小任务,每个任务分散在Ruby的执行中,这样单次的暂停时间就会变得很短

GC触发条件

  1. 少于free slots时执行GC (RUBY_GC_HEAP_FREE_SLOTS)
  2. mallocate 分配16MB以上内存时执行GC ( RUBY_GC_MALLOC_LIMIT ~ RUBY_GC_MALLOC_LIMIT_MAX 32MB区间个字节 具体数字是Ruby根据内存使用情况动态确定的)

系统变量说明:
RUBY_GC_HEAP_INIT_SLOTS: Ruby初始化slots数量,数量越大后续malloc次数越小
RUBY_GC_HEAP_GROWTH_FACTOR:内存分配增长因子
RUBY_GC_MALLOC_LIMIT: GC触发条件之一, 分配内存超过限制后运行GC
RUBY_GC_HEAP_FREE_SLOTS:对堆空间free slot少于该参数时触发GC

如何查看页面分配了多少内存?

rack-mini-profiler包含一个非常方便的报告,用于获取各种页面中内存使用的句柄。 只需在您的网址末尾附加 ?pp=profile-gc :
http://test.com/gc_check?pp=profile-gc

Overview
--------
Initial state: object count: 583769
Memory allocated outside heap (bytes): 109579497

GC Stats:
--------
count : 86
heap_allocatable_pages : 0
heap_allocated_pages : 2602
heap_available_slots : 1060592
heap_eden_pages : 2602
heap_final_slots : 0
heap_free_slots : 238107
heap_live_slots : 822485
heap_marked_slots : 822484
heap_sorted_length : 2602
heap_tomb_pages : 0
major_gc_count : 16
malloc_increase_bytes : 26568
malloc_increase_bytes_limit : 19983014
minor_gc_count : 70
old_objects : 808267
old_objects_limit : 1616534
oldmalloc_increase_bytes : 2575192
oldmalloc_increase_bytes_limit : 46281415
remembered_wb_unprotected_objects : 11399
remembered_wb_unprotected_objects_limit : 22798
total_allocated_objects : 9572514
total_allocated_pages : 2602
total_freed_objects : 8750029
total_freed_pages : 0

New bytes allocated outside of Ruby heaps: 492677
New objects: 6141

ObjectSpace delta caused by request:
-----------------------------------
String : 4241
Array : 735
MatchData : 414
Hash : 321
Integer : 106
ActiveRecord::MigrationProxy : 92
Proc : 49
NewRelic::Agent::Attributes : 28
NewRelic::Agent::Transaction::Segment : 28
IPAddr : 14
Time : 11

可以看到本次访问导致分配了0.5M的内存,6141个新的对象。
注意:由于在禁用 GC 的情况下通过 ObjectSpace 进行迭代,不可避免地运行此报告可能会导致 Ruby 堆增长。 建议您在分析会话后循环生产过程。

count: GC运行的次数,它是由minor_gc_count + major_gc_count 来组成的
minor_gc_count: GC进行的一次小型的回收
major_gc_count: GC进行的是一次full垃圾回收
heap_live_slot: GC运行以来,还存活的对象的个数
heap_free_slots: 空闲slot个数,也就是可以用来存放新的对象的slot的个数

内存优化 jemalloc

GitHub - jemalloc/jemalloc
Ruby 传统上使用 C 语言函数 malloc 在存储对象时动态分配、释放和重新分配内存。 Jemalloc 是由 Jason Evans 开发的 malloc(3) 实现(因此 malloc 开头的首字母为“je”),与其他分配器相比,jemalloc 比其他分配器更好地处理小对像,因为它专注于避免碎片和可扩展的并发支持.

jemalloc 的使用还是比较简单的只需要下载安装,配置一个系统环境变量再重新启动应用成就可以应用jemalloc了,而效果经过我的观察大概可以降低20%~30%的内存占用。
安装jemalloc:

wget https://*.aliyuncs.com/jemalloc-5.3.0.tar.bz2
tar -jxvf jemalloc-5.3.0.tar.bz2
cd jemalloc-5.3.0
./autogen.sh
make
make install
# ~/.bashrc
export LD_PRELOAD=$LD_PRELOAD:/usr/local/lib/libjemalloc.so.2

然后执行:

source ~/.bashrc
echo $LD_PRELOAD

然后重启Ruby应用就可以了,注意 unicorn.sh restart 是不行的,必须停掉进程再重新启动才生效

验证是否使用jemalloc:

lsof -n|grep jemalloc

效果见confluence

glibc_malloc.png

jemalloc.png

FullStap Ruby

由REE 的创建者、Phusion 的首席技术官 Hongli Lai 发布的Fullstaq Ruby。
为什么会有fullstaq ruby?
对于在服务器用例(例如大多数 Ruby 网络应用程序)中使用 Ruby 的人来说,Ruby 核心开发人员有点谨慎/保守并且对采用 Jemalloc 犹豫不决,理由是担心 Jemalloc 可能会导致非服务器用例中的回归,以及 作为其他与兼容性相关的问题。
这就是 Fullstaq Ruby 的用武之地:我们固执己见。 Fullstaq Ruby 不关心非服务器用例,我们喜欢更优的方法,因此我们选择针对该服务器应用进行优化。

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

推荐阅读更多精彩内容

  • 性能优化是使用Ruby开发web应用中一个比较头痛的问题,因为Ruby本身的执行性能并不高,而且重量级应用Rail...
    falm阅读 2,097评论 0 4
  • 总结: 内存碎片是一个难以测量和诊断的问题,但是解决这个问题有时也很容易。让我们看看在CRuby程序中,导致内存碎...
    渔人Early阅读 1,276评论 1 1
  • 众所周知,Ruby(MRI)中有三大神秘区域: GC GIL 编译/执行 我就是CRuby的GC,虽然大多数人不了...
    渔人Early阅读 1,004评论 0 0
  • 本篇是在我接触了 Ruby 很短一段时间后有幸捧起的一本书,下面结合自己的一些思考,来输出一下自己的读书笔记 前言...
    我没有三颗心脏阅读 532评论 3 2
  • 相信大家在学习C语言的时候,malloc是最早遇到的几个方法之一,这里就来深入的了解下,macOS/iOS中用户空...
    码农苍耳阅读 7,978评论 2 14