美文网首页
从Lua5.1.4源码来分析Lua的GC机制(一)

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

作者: 凉拌姨妈好吃 | 来源:发表于2018-06-29 01:41 被阅读0次

注意: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

相关文章

网友评论

      本文标题:从Lua5.1.4源码来分析Lua的GC机制(一)

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