Lua内存管理

本文分析基于的Lua版本为:Lua 5.3.0

Lua内存管理器规约



Lua允许用户自定义内存管理器,并在创建Lua虚拟机时传入。当然该内存管理器必须遵循Lua已定义的行为规则。

我们已经知道创建一个Lua虚拟机需要使用luaL_newstate函数:

lua_State *L = luaL_newstate();

luaL_newstate函数内部实现主要是调用lua_newstate函数,lua_newstate函数将会接受一个内存分配器作为参数,进而在内部分配内存:

// lauxlib.h
LUALIB_API lua_State *(luaL_newstate) (void);

// lauxlib.c
LUALIB_API lua_State *luaL_newstate (void) {
  lua_State *L = lua_newstate(l_alloc, NULL);if (L) lua_atpanic(L, &panic);
  return L;
}

// lua.h
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);

// lstate.c
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  int i;
  lua_State *L;
  global_State *g;
  LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
  if (l == NULL) return NULL;
  ......  return L;
}

可以看到,Lua内存分配器必须是lua_Alloc类型的函数,如果用户想自定义内存分配器,那么用户定义的内存分配器必须是一个lua_Alloc类型的函数:

// lua.h
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);

Lua默认内存管理器



luaL_newstate函数内部调用lua_newstate函数时,传入了一个lua_Alloc类型的函数:l_alloc,该函数即Lua内部提供的默认内存管理函数。该函数主要使用C标准库中的realloc函数进行内存分配:

// lauxlib.c
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
  (void)ud; (void)osize;  /* not used */
  if (nsize == 0) {
    free(ptr);
    return NULL;
  }
  else
    return realloc(ptr, nsize);
}

       ud  :默认内存管理器并未使用该参数,不过在用户自定义内存管理器中,可以让内存管理在不同的堆上进行。

       ptr :非空表示指向一个已分配的内存块指针,NULL表示将分配一块nsize大小的新内存块。

       osize:原始内存块大小,默认内存管理器并未使用该参数。Lua强制在调用内存管理器时候需要给出原始内存块的大小,如果用户需要自定义一个高效的内存管理器,那么这个参数信息将十分重要。大多数的内存管理算法中都需要为内存块加上一个cookie,里面存储了内存块尺寸的信息,以便在释放内存的时候能够获取到内存块的尺寸信息(譬如多级内存池)。而Lua内存管理器刻意提供了这个信息,这样就不必额外存储这些cookie信息,在大量使用小内存块的环境中将可以节省不少的内存。另外在ptr传入NULL时,osize表示Lua对象类型(LUA_TNIL、LUA_TBOOLEAN、LUA_TTHREAD等等),这样内存管理器就可以知道当前在分配的对象的类型,可以针对它做一些统计或优化的工作。

       nsize:新内存块大小,特别地在nsize为0时需要提供内存释放的功能。

global_State.frealloc



我们已经知道在创建Lua虚拟机时将传入一个内存管理器,并使用该内存管理器分配相关的数据结构。Lua设计了一个global_State结构(全局状态机)来存储各种全局数据,其中就包含了内存管理器。也就是说我们使用内存管理器创建了一个Lua虚拟机,Lua虚拟机的全局状态机中保存了该内存管理器,以便Lua后续内部的工作能使用该内存管理器进行管理。

// lstate.c
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  int i;
  lua_State *L;
  global_State *g;
  LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
  if (l == NULL) return NULL;
  L = &l->l.l;
  g = &l->g;
  L->next = NULL;
  L->tt = LUA_TTHREAD;
  g->currentwhite = bitmask(WHITE0BIT);
  L->marked = luaC_white(g);
  preinit_thread(L, g);
  g->frealloc = f;
  ......

  return L;
}

Lua内存管理的宏



Lua设计了一组宏来管理不同类别的内存:单个对象、数组、可变长数组等等。这一系列的宏使用了两个核心API:luaM_realloc_luaM_growaux_,下面我们先就这两个核心API进行分析。

luaM_realloc

luaM_realloc_函数并不会被直接调用,它将调用保存在global_State中的内存分配器进行内存管理工作。luaM_realloc_会根据传入的osize和nsize调整内部感知的内存大小(设置GCdebt),在内存不够用的时候会主动尝试做GC操作。

// lmem.h/* not to be called directly */LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,                                                          size_t size);

// lmem.c
/*
** generic allocation routine.
*/
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
  void *newblock;
  global_State *g = G(L);
  size_t realosize = (block) ? osize : 0;
  lua_assert((realosize == 0) == (block == NULL));
#if defined(HARDMEMTESTS)
  if (nsize > realosize && g->gcrunning)
    luaC_fullgc(L, 1);  /* force a GC whenever possible */
#endif
  newblock = (*g->frealloc)(g->ud, block, osize, nsize);
  if (newblock == NULL && nsize > 0) {
    api_check( nsize > realosize,
                 "realloc cannot fail when shrinking a block");
    luaC_fullgc(L, 1);  /* try to free some memory... */
    newblock = (*g->frealloc)(g->ud, block, osize, nsize);  /* try again */
    if (newblock == NULL)
      luaD_throw(L, LUA_ERRMEM);
  }
  lua_assert((nsize == 0) == (newblock == NULL));
  g->GCdebt = (g->GCdebt + nsize) - realosize;
  return newblock;
}

luaM_growaux_

       luaM_growaux_函数是用来管理可变长数组的,其主要策略是:当数组空间不够时,扩大为原来的两倍。其中管理内存部分使用了基于luaM_realloc_函数的宏luaM_reallocv,该宏针对数组操作,根据新的数组元素个数重新分配内存。

// lmem.h
/* not to be called directly */
LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size,
                               size_t size_elem, int limit,
                               const char *what);

// lmem.c
#define MINSIZEARRAY    4

void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems,
                     int limit, const char *what) {
  void *newblock;
  int newsize;
  if (*size >= limit/2) {  /* cannot double it? */
    if (*size >= limit)  /* cannot grow even a little? */
      luaG_runerror(L, "too many %s (limit is %d)", what, limit);
    newsize = limit;  /* still have at least one free place */
  }
  else {
    newsize = (*size)*2;
    if (newsize < MINSIZEARRAY)
      newsize = MINSIZEARRAY;  /* minimum size */
  }
  newblock = luaM_reallocv(L, block, *size, newsize, size_elems);
  *size = newsize;  /* update only when everything else is OK */
  return newblock;
}

       size:数组元素个数,传入表示原始数组大小,传出表示重新分配后数组大小;

       size_elem:单个元素大小;

       limit:数组元素最大个数限制;

       what:提示信息字符串;

• luaM_reallocv

/*** This macro reallocs a vector ‘b‘ from ‘on‘ to ‘n‘ elements, where** each element has size ‘e‘. In case of arithmetic overflow of the** product ‘n‘*‘e‘, it raises an error (calling ‘luaM_toobig‘). Because** ‘e‘ is always constant, it avoids the runtime division MAX_SIZET/(e).**** (The macro is somewhat complex to avoid warnings:  The ‘sizeof‘** comparison avoids a runtime comparison when overflow cannot occur.** The compiler should be able to optimize the real test by itself, but** when it does it, it may give a warning about "comparison is always** false due to limited range of data type"; the +1 tricks the compiler,** avoiding this warning but also this optimization.)*/#define luaM_reallocv(L,b,on,n,e) \
  (((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e))       ? luaM_toobig(L) : cast_void(0)) ,    luaM_realloc_(L, (b), (on)*(e), (n)*(e)))

luaM_reallocv将使数组b的长度(最大容纳元素个数)从on重新分配为n,其中每个数组元素大小为e。
       b  :数组指针;

       on  :数组重新分配前的长度(最大容纳元素个数);

       n    :数组重新分配后的长度(最大容纳元素个数);

       e  :数组元素大小;

• luaM_reallocvchar

/*
** Arrays of chars do not need any test
*/
#define luaM_reallocvchar(L,b,on,n)  \
    cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))

luaM_reallocvchar将使字符数组b的长度(最大容纳元素个数)从on重新分配为n,其中每个数组元素大小为sizeof(char)。

       b  :数组指针;

       on  :数组重新分配前的长度(最大容纳元素个数);

       n  :数组重新分配后的长度(最大容纳元素个数);

• luaM_freemem

#define luaM_freemem(L, b, s)    luaM_realloc_(L, (b), (s), 0)

luaM_freemem将释放b指向的内存块空间。

       b  :内存块指针;

       s     :内存块大小;

• luaM_free

#define luaM_free(L, b)        luaM_realloc_(L, (b), sizeof(*(b)), 0)

luaM_free将释放b指向的内存块空间(b表示某种对象类型指针)。

       b  :内存指针,同时表示某种对象类型指针;

• luaM_freearray

#define luaM_freearray(L, b, n)   luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0)

luaM_freearray将释放b指向的内存块空间(b表示某种类型对象的数组指针)。

       b  :内存指针,同时表示某种类型对象的数组指针;

       n  :数组长度(最大容纳元素个数);

• luaM_malloc

#define luaM_malloc(L,s)    luaM_realloc_(L, NULL, 0, (s))

luaM_malloc将分配一块大小为s的内存块空间。

       s  :将要分配的内存块空间大小;

• luaM_new

#define luaM_new(L,t)        cast(t *, luaM_malloc(L, sizeof(t)))

luaM_new将分配一块内存块空间,空间大小为sizeof(t)。

        t  :某种数据类型;

• luaM_newvector

#define luaM_newvector(L,n,t) \
        cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t)))

luaM_newvector将分配一个长度为n的数组空间,数组元素为类型t。

       n  :数组长度(最大容纳元素个数);

       t  :数组元素类型;

• luaM_newobject

#define luaM_newobject(L,tag,s)    luaM_realloc_(L, NULL, tag, (s))

luaM_newobject将分配一块大小为s的内存块空间,其将要容纳的Lua数据类型为tag表示的类型。

       tag   :Lua数据类型;

       s  :分配的内存块大小;

• luaM_growvector

#define luaM_growvector(L,v,nelems,size,t,limit,e)           if ((nelems)+1 > (size))             ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))

luaM_growvector将在数组空间不足以容纳下一个元素的情况下增长空间大小(原空间大小 * 2)。

       v             :数组指针;

       nelems   :正在使用的元素个数;

       size    :数组元素个数,传入表示原始数组大小,传出表示重新分配后数组大小;

       t         :(数组元素的)数据类型;

       limit   :数组元素最大个数限制;

       e    :提示信息字符串;

• luaM_reallocvector

#define luaM_reallocvector(L, v,oldn,n,t) \
   ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t))))

luaM_reallocvector将重新分配数组空间大小。

        v  :数组指针;

        oldn :重新分配前数组大小;

        n  :重新分配后数组大小;

时间: 2024-11-09 02:37:07

Lua内存管理的相关文章

菜鸟学习Cocos2d-x 3.x——内存管理

菜鸟学习Cocos2d-x 3.x——内存管理 2014-12-10 分类:Cocos2d-x / 游戏开发 阅读(394) 评论(6) 亘古不变的东西 到现在,内存已经非常便宜,但是也不是可以无限大的让你去使用,特别是在移动端,那么点内存,那么多 APP要抢着用,搞不好,你占的内存太多了,系统直接干掉你的APP,所以说了,我们又要老生常谈了——内存管理.总结COM开发的时候,分析过COM的 内存管理模式:总结Lua的时候,也分析了Lua的内存回收机制:前几天,还专门写了C++中的智能指针在内存

内存管理笔记

目前实现智能管理内存的技术,一是引用计数,一是垃圾回收. 引用计数:是一种很有效的机制,通过给没个对象维护一个引用计数器,记录该对象当前呗引用的次数.当对象增加一次引用时,计数器加1:而对象失去一次引用时,计数器减1:当引用计数为0时,标志着该对象的生命周期结束,自动触发对象的回收释放.引用计数的重要规则是每个程序片段必须负责任地维护引用计数,在需要维持对象生存的程序段的开始和结束分别增加或减少一次引用计数,这样就能实现十分灵活的智能内存管理. 垃圾回收:它通过引用一种自动的内存回收器,试图将程

Cocos2d之&ldquo;引用计数&rdquo;内存管理机制实现解析

一.引言 本文主要分析cocos2d游戏开发引擎的引用计数内存管理技术的实现原理.建议读者在阅读本文之前阅读笔者之前一篇介绍如何使用cocos2d内存管理技术的文章--<Cocos2d之Ref类与内存管理使用详解>. 二.相关概念 引用计数 引用计数是计算机编程语言的一种内存管理技术,是指将资源(对象.内存或者磁盘空间等)的被引用计数保存起来,当引用计数变为零时就将资源释放的过程.使用引用计数技术可以实现自动内存管理的目的. 当实例化一个类时,对象的引用计数为1,在其他对象需要持有这个对象时,

cocos2dx 之内存管理

 cocos2dx的内存管理移植自Objective-C, 对于没有接触过OC的C++开发人员来说是挺迷惑的.不深入理解内存管理是无法写出好的C++程序的,我用OC和cocos2dx也有一段时间了,在此总结一下,希望对想用cocos2dx开发游戏的朋友有所帮助. C++的动态内存管理一般建议遵循谁申请谁释放的原则,即谁通过new操作符创建了对象,谁就负责通过delete来释放对象.如果对象的生命周期在一个函数内,这很容易做到,在函数返回前delete就行了.但一般我们在函数中new出来的对象

8、Cocos2dx 3.0游戏开发找小三之3.0版本的内存管理

重开发者的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27693365 复杂的内存管理 移动设备上的硬件资源十分有限,内存尤为宝贵,开发者必须十分慎重地利用内存,避免不必要的消耗,更要防止内存泄漏. 基于 Cocos2d-iPhone 的 Objective-C风格的内存管理是 Cocos2d-x 的一个特色. 把 Objective-C 的内存管理方式引入 C++,使得游戏开发的内存管理难度下降了个层次.

cocos2d-x 3.0 内存管理机制

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 再来一弹,内存管理机制 1.简言机制 2.代码观机制 1.简言-Cocos2d-x的内存管理机制 说到内存管理,这是个question,(⊙v⊙)嗯.. 看一下各语言怎么进行内存管理吧? --JAVA: 堆区的就是new来分配内存.通过垃圾回收机制来回收. (详

Unity开发者的C#内存管理(上篇)

本文翻译自:C# Memory Management for Unity Developers (part 1 of 3) 很多游戏时常崩溃,大多数情况下都是内存泄露导致的.这系列文章详细讲解了内存泄露的原因,如何找到泄露,又如何规避. 我要在开始这个帖子之前忏悔一下.虽然一直作为一个C / C++开发者,但是很长一段时间我都是微软的C#语言和.NET框架的秘密粉丝.大约三年前,当我决定离开狂野的基于C / C++的图形库,进入现代游戏引擎的文明世界,Unity 带着一个让我毫不犹豫选择它的特性

Lua内存分析工具

最近给公司写了一个lua内存分析工具,可以方便的分析出Lua内存泄露问题(虽然还没正式使用,但我是这样想的,哈哈哈),有图形化界面操作,方便手机端上传快照等功能 内存分析我是在c语言端写的,也有人写过lua端的分析工具,也蛮好用的,不过lua分析工具本身也会影响到lua的内存占用(尽管用的是弱表缓存的),也会有些不准确. Lua方案:https://github.com/yaukeywang/LuaMemorySnapshotDump 然后找到了云风大神写的C语言解决方案 https://blo

linux内存管理

一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程序可调用它.假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段.     2) 数据段:存放已初始化的全局变量.静态变量(包括全局和局部的).常量.static全局变量和static函数只能在当前文件中被调用.     3) 未初始化数据区(uninitializeddata s