从Lua5.1.4源码来分析Lua的GC机制(一)

注意:5.1之后就开始使用增量式的收集器,也就是说它是隔行扫描的方式与解释器一起工作。而5.1之前,收集器的运行会暂停对整个程序的响应。这一个改变还是非常有用处的!

1. gc的状态阶段

gc被分为下面五个阶段:标记、整理、清扫字符串、清扫、收尾
状态值的大小代表了它们的执行顺序,越小越先执行。

#define GCSpause    0
#define GCSpropagate    1
#define GCSsweepstring  2
#define GCSsweep    3
#define GCSfinalize 4

2. GCSpause

GCSpause只是用来标记系统的根节点


GCSpause的执行

Lua认为每个需要被GC管理的对象都有颜色,一开始,所有节点都是白色的。在我们标记阶段,将节点逐个被设置为黑色。有一些节点因为还存在子节点还没有处理,所以为灰色。在下面的源码中我们可以看出,根节点含有其他子节点,所以根节点我们先将它设置为灰色。

markroot的实现方式

在上面的源码中我们还发现了markobject这个方法,点进去看一下它到底是如何标记的,它调用了另外一个函数reallymarkobject:

static void reallymarkobject (global_State *g, GCObject *o) {
  lua_assert(iswhite(o) && !isdead(g, o));
  white2gray(o);
  switch (o->gch.tt) {
    case LUA_TSTRING: {
      return;
    }
    case LUA_TUSERDATA: {
      Table *mt = gco2u(o)->metatable;
      gray2black(o);  /* udata are never gray */
      if (mt) markobject(g, mt);
      markobject(g, gco2u(o)->env);
      return;
    }
    case LUA_TUPVAL: {
      UpVal *uv = gco2uv(o);
      markvalue(g, uv->v);
      if (uv->v == &uv->u.value)  /* closed? */
        gray2black(o);  /* open upvalues are never black */
      return;
    }
    case LUA_TFUNCTION: {
      gco2cl(o)->c.gclist = g->gray;
      g->gray = o;
      break;
    }
    case LUA_TTABLE: {
      gco2h(o)->gclist = g->gray;
      g->gray = o;
      break;
    }
    case LUA_TTHREAD: {
      gco2th(o)->gclist = g->gray;
      g->gray = o;
      break;
    }
    case LUA_TPROTO: {
      gco2p(o)->gclist = g->gray;
      g->gray = o;
      break;
    }
    default: lua_assert(0);
  }
}

可以看出,字符串对象并没有任何的操作直接返回

  • userdata是将它本身及它的元表和环境表标为黑色(这是一个递归的过程,不断去找寻它的元表)
  • upValue也就是自由变量,当它脱离了它的作用域之后就会被设置为黑色,这里大家可能会对 if (uv->v == &uv->u.value) 产生疑惑,没关系,我们再深入研究一下UpValue的概念以及它的新建与关闭。
  • 其他都是函数、表、线程。它们就是通过对象保存的上一阶段的全局灰链,然后将自身赋予灰链,并且标记自己为黑色,这样就能把强引用的对象连接起来,在扩散标记阶段,处理标记灰链中对象的时候,处理完子引用后,就可以通过对象的gclist指针恢复上一个灰链。 (?不是很理解)
    Lua引用的实现
    通过为每个变量至少创建一个upvalue 并按所需情况进行重复利用,保证了未脱离作用域的局部变量可以在闭包里正确使用。为了保证它的唯一性,Lua为整个运行栈保存了一个链接着所有正打开(正指向栈内的局部变量)的upValue的链表。所以当Lua在创建一个新的闭包的时候,就会遍历外面的所有局部变量,对于这些变量,如果在链表里有找到它,就重用。否则创建一个新的upValue并保存到链表中。
    再了解一下upValue的结构(看注释即可明白)
typedef struct UpVal {  
  CommonHeader;  
  TValue *v;  /* points to stack or to its own value */  
  union {  
    TValue value;  /* the value (when closed) */  
    struct {  /* double linked list (when open) */  
      struct UpVal *prev;  
      struct UpVal *next;  
    } l;  
  } u;  
} UpVal;

如下我们可以看到,不断的遍历外部的局部变量,如果不存在upValue的就新建它,那么此时它就是不关闭的

UpVal *luaF_findupval (lua_State *L, StkId level) {  
  global_State *g = G(L);  
///得到openupval链表  
  GCObject **pp = &L->openupval;  
  UpVal *p;  
  UpVal *uv;  
///开始遍历open upvalue。  
  while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {  
    lua_assert(p->v != &p->u.value);  
///发现已存在。  
    if (p->v == level) {    
      if (isdead(g, obj2gco(p)))  /* is it dead? */  
        changewhite(obj2gco(p));  /* ressurect it */  
///直接返回  
      return p;  
    }  
    pp = &p->next;  
  }  
///否则new一个新的upvalue  
  uv = luaM_new(L, UpVal);  /* not found: create a new one */  
  uv->tt = LUA_TUPVAL;  
  uv->marked = luaC_white(g);  
///设置值  
  uv->v = level;  /* current value lives in the stack */  
///首先插入到lua_state的openupval域  
  uv->next = *pp;  /* chain it in the proper position */  
  *pp = obj2gco(uv);  
///然后插入到global_State的uvhead(这个也就是双向链表的头)  
  uv->u.l.prev = &g->uvhead;  /* double link it in `uvhead' list */  
  uv->u.l.next = g->uvhead.u.l.next;  
  uv->u.l.next->u.l.prev = uv;  
  g->uvhead.u.l.next = uv;  
  lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);  
  return uv;  
}  

所以我们才在下面的if语句里判断upValue是否为关闭,如果为关闭就将它标记为黑色。未关闭表示现在局部变量还没有出作用域!所以不能标记为黑!


upValue

这里有两个白色标记位。在GC标记结束但是清理流程尚未作完前,一旦对象间的关系产生了变化,比如有新增对象的时候,我们不知道它的生命期,那么我们就不能够将它简单的设置为黑色,所以这时候就引出了一个第二种白色的概念


节点的颜色存储

GCSpause 阶段执行完,立刻就将状态切换到了 GCSpropagate ,我们下篇再来深入讲解GCSpropagate

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

推荐阅读更多精彩内容