ObjectSpace
在Ruby堆空间中维护一个对象池,这个对象池也被称作ObjectSpace
, 你所使用的所有Ruby对象都是从这个池子中取出的,而我也会去清理池子中已经没有被使用的对象,达到循环利用的目的。
ObjectSpace
对象池是由很多堆页(page)构成的,每一个页的大小为16Kb。 每页中包含408个槽(slot)。 一个槽对应一个对象,你所使用的每一个对象都在对象池中占有一个槽。这些数据可以通过下面的方式得到:
GC::INTERNAL_CONSTANTS
=> {:RVALUE_SIZE=>40, # 一个RVALUE结构体40个字节:HEAP_PAGE_OBJ_LIMIT=>408,# ...}
Ruby有这样一种类似于C语言中结构体的结构,Ruby中称其为RValue 结构,对象被存放在这个结构体中,除了对象之外,还附有一个flag,也就是在mark-and-sweep机制被mark时要使用的flag。
例如: RString (RArray,RHash,RFile)
内存中,Ruby将许多这样的RValue结构体组织在一种被称为heap的数组里面。在一个进程当中,有许多这样的heap数组存在。每个RValue结构体的大小是40bytes,这也预示着这里不能存放较大的对象,故Ruby将一些较小的常用的String, Array或Hash等对象存放在此处。
例如:这是一个Ruby堆数组的概念图,包含三个字符串值以及许多其他RValue:
Ruby 1.8:简单的标记和扫描
ruby中的GC使用的是 mark and sweep机制,ruby会从根目录扫描全部对象,如果能 reach到这个对象,就使用名为FL_MARK的内部标志之一标记变量,扫描完毕没有被 mark的对象就会被回收。扫描过程是处于 Stop The word 暂停状态。
根对象
是当前Ruby程序明显需要
的,包括(不保证完整):
-
全局变量(类、常量等)
-
本地变量(Ruby当前堆栈信息)
-
C 寄存器及堆栈数据
Ruby 1.9.3:懒惰扫描
标记完成后,不一次性清除所有无用的对象,将清除的过程分散到后面的操作中,这大大降低了单次清除过程中的中断时间。
Ruby 2.0中的垃圾收集:位图标记
不是在每个RValue结构中使用FL_MARK位来指示Ruby仍在使用值并且无法释放它,而是将Ruby 2.0中的信息保存在称为“位图”的内容中(位图指映射回RValue结构的文字位集合)
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 module中,为我们提供了一个GC.stat方法来查看GC的状态:
2.2.8 :001 > GC.stat
{ :count => 9,
:heap_allocated_pages => 74,
:heap_sorted_length => 75,
:heap_allocatable_pages => 0,
:heap_available_slots => 30168,
:heap_live_slots => 29769,
:heap_free_slots => 399,
:heap_final_slots => 0,
:heap_marked_slots => 14081,
:heap_swept_slots => 8298,
:heap_eden_pages => 74,
:heap_tomb_pages => 0,
:total_allocated_pages => 74,
:total_freed_pages => 0,
:total_allocated_objects => 121550,
:total_freed_objects => 91781,
:malloc_increase_bytes => 12768,
:malloc_increase_bytes_limit => 16777216,
:minor_gc_count => 6,
:major_gc_count => 3,
:remembered_wb_unprotected_objects => 182,
:remembered_wb_unprotected_objects_limit => 362,
:old_objects => 13837,
:old_objects_limit => 26864,
:oldmalloc_increase_bytes => 13280,
:oldmalloc_increase_bytes_limit => 16777216
}
GC.stat
给我们返回了一个Hash,其中count所指向的值是GC运行的次数,它是由minor_gc_count + major_gc_count 来组成的。其中minor_gc_count指的是GC进行的一次小型的回收,而major_gc_count则是GC进行的是一次full垃圾回收。之所以这样区分开来,是因为每次minor回收所需要使用的时间远小于major使用的时间。每次进行GC回收的时候,会消耗很大的资源和性能。根据情况进行minor回收能够较好的实现性能的提升;heap_live_slot指的是在GC运行以来,还存活的对象的个数,heap_free_slots指的是用来存放新的对象的slot的个数。
使用GC#start /ObjectSpace.garbage_collect
来回收已作废的对象(进行的 full垃圾回收)。
Ruby在短时间内大量对象被创建的时候,是会申请较多的内存来处理的。即使在free掉相关对象后不将这部分内存归还给操作系统,因为ruby在下次分配对象的时候不需要时刻都向操作系统请求内存资源,而可以直接使用原来使用过的free_slot。
所以:
1、尽量避免创建极大的对象; 尽量复用旧对象。
2、在rails和ruby中,尽量不要把对象赋予给全局的变量或是类或module的属性,这会导致这些对象一直被保留而无法释放。
3、尽量使用最新版本的Ruby,GC的性能会更好
ruby中GC的演化: http://tmm1.net/ruby21-rgengc/
关于GC机制: http://patshaughnessy.net/2012/3/23/why-you-should-be-excited-about-garbage-collection-in-ruby-2-0
关于ruby中栈堆:http://patshaughnessy.net/2012/1/18/seeing-double-how-ruby-shares-string-values