Ruby的GC机制源码分析(3)

对象的管理

ruby GC 的目标只是Ruby 的对象。而且一定要是ruby 生成和管理的对象。反过来说,它无法照料到用户随意分配的内存。比如,下面的函数在ruby 的操作中就会引起内存泄漏

 

void not_ok(){

malloc(1024);  /* 获得内存后丢弃 */

}

然而下面的函数不会引起内存泄漏。

 

void this_is_ok(){

rb_ary_new();  

/*
生成Ruby 数组后丢弃 */
}

rb_ary_new() 使用了ruby 的正式接口分配内存,所以会在ruby GC 的管理之下,由ruby 照料。

 

 

struct RVALUE

对象的实体是结构体,对象的管理就是对这个结构体的管理。当然,非指针的Fixnum Symbol nil true false 例外,太麻烦,这里就不一一写了。

实体结构体的大小因类型而不同,恐怕是为了避免管理上的麻烦,内建类的结构体声明为共用体,并通过共用体访问内存。共用体声明如下。

RVALUE




struct RVALUE 是一个只有一个元素的结构体。不直接使用union 是为了调试或将来扩展时添加成员的方便。

首先来关注一下共用体的第一个元素free.flags 。注释中写着 不用时为0” ,这是真的吗?难道使用中的对象free.flags 不能偶然为0 吗?

正如在第2 章《对象》中看到的那样,所有的对象构造体其第一个元素都是struct RBasic 。因此,无论从共用体的哪个元素访问,写成obj->as.free.flags 和写成obj->as.basic.flags 都是一样的。对象在标志位都有一个结构体类型标志(T_STRING 等),而且,所有的标志都是非0 值,所以, 活着 的对象其标志不会偶然为0 。换句话说,可以确认,标志为0 对象的充分必要条件。

 

对象堆( object heap

所有对象结构的内存都在全局的变量堆上。下面这个叫做对象堆。

对象堆



heaps struct RVALUE 数组的数组。heaps 中保存的是一个个的heap heap 的元素就是一个个的slot (图9 )。

heaps、heap、slot
9: heaps heap slot

heaps 的长度heaps_length 是可变的。实际用到的槽的个数保存heaps_used 中。每个heap 的长度对应保存在heaps_limits[index] 中。也就是说,对象堆的结构如图10 所示。

在内存上展开的heap的概念图
10: 在内存上展开的heap 的概念图

这个结构有其必然性。比如,当所有结构都配置到一个数组中时,内存空间最为紧凑,但是由于地址可能发生变化,不能使用realloc() ,因为VALUE 就是单纯的指针。

对应于Java 实现,对象是可以移动的,因为它是通过对象表来处理的,VALUE 是对象的索引,而非地址。然而,每次访问对象多要对数组进行索引,性能会有所下降。

另一方面,把RVALUE 的指针(也就是VALUE )做成一个一维数组会怎么样呢?乍看起来,一切顺利,但GC 的时候会遇到问题。正如后面会详细讨论的,因为ruby GC 需要知道一个整数是否是VALUE (指向RVALUE 的指针)一样 。所有的RVALUE 配置到不相关的地址之后,所有RVALUE 的地址要分配同所有 可能是地址 的整数进行比较。它会让GC 挂起的时间变成O(n^2) 以上的数量级,这是无法容忍的。

综上所述,对象堆是在某种程度上将地址相关,且位置和总量不受限制的结构。

freelist

未使用RVALUE 是由一个以freelist 开头的链表管理的。RVALUE as.free.next 就是为此准备的链。

freelist

236  
static RVALUE *freelist = 0

(gc.c)


add_heap()

了解数据结构之前,先来看看添加堆的函数add_heap() 。这个函数主线之外的描述很杂乱,除去错误处理和转型部分,可以得到一个简化版本。

add_heap() (简化版)

static void add_heap()
{
RVALUE *p, *pend;

/* 
必要的话扩展heaps */


if (heaps_used == heaps_length) {

heaps_length += HEAPS_INCREMENT;

heaps  = realloc(heaps, 

heaps_length * sizeof(RVALUE*));

heaps_limits = realloc(heaps_limits, heaps_length * sizeof(int));

}

/* 
增加一个heap */

p = heaps[heaps_used] = malloc(sizeof(RVALUE) * heap_slots);
heaps_limits[heaps_used] = heap_slots;

pend = p + heap_slots;

if (lomem == 0 || lomem > p) lomem = p;

if (himem < pend) himem = pend;

heaps_used++;

heap_slots *= 1.8;

/* 
分配的RVALUE
连接到freelist */


while (p < pend) {
p->as.free.flags = 0;
p->as.free.next = freelist;
freelist = p;

p++;
}
}


以下几点需要确认。

 

  • heap 的长度是 heap_slots
  • 每增加一个 heap heap_slots 变为原来的 1.8
  • heaps[i] 的长度(生成堆时 heap_slots 的值)保存在 heaps_limits[i]

再有,只有这个函数修改lomem himem ,也只有从这个函数理解其机制。这两个变量分别是对象堆的最下端地址和最上端地址。这个值稍后还会用于判断一个整数是否是VALUE 一样 的。

rb_newobj()

综合以上几点,就能够知道对象生成的方法。如果freelist 没有相连的RVALUE ,就会去做GC ,或是增加堆。通过阅读对象生成的函数rb_newobj() ,我们可以确认这一点。

rb_newobj()




如果freelist 0 ,也就是,没有剩余的结构体,就启动GC ,创建一个区域。即便一个对象都无法收回,rb_gc() 还可以分配一个新的区域,这是毫无疑问的。并且,从freelist 中取出一个结构体,通过MEMZERO() 0 填充它,然后返回它。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值