美文网首页
Ruby 内存分配

Ruby 内存分配

作者: bruce_wu | 来源:发表于2022-11-21 16:25 被阅读0次
    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 不关心非服务器用例,我们喜欢更优的方法,因此我们选择针对该服务器应用进行优化。

    相关文章

      网友评论

          本文标题:Ruby 内存分配

          本文链接:https://www.haomeiwen.com/subject/gsgbxdtx.html