天天看點

Cocos、Lua遊戲記憶體釋放之我見

本系列針對cocos2dx+Lua遊戲開發,在本篇中主要Lua對象釋放的姿勢,避免造成記憶體洩漏,有寫得不好或者寫錯的地方,麻煩大家幫忙指正。

介紹之前,我們先了解一下遊戲運作時,最基本的記憶體占用。(這裡的劃分并不标準,隻是為了形象介紹劃分出來的)

1、貼圖記憶體

2、代碼運作記憶體

其中貼圖記憶體主要包括貼圖資源和字型資源。代碼運作記憶體包括平台層,C++層,Lua層等,相對于貼圖記憶體來說,占比較小,但往往一些寫法用法不當或導緻記憶體洩漏和貼圖記憶體洩漏。

在這一課我們介紹的是C++對象以及Lua對象釋放的姿勢,其它的暫不作介紹。

cocos2dx移植于Objective-C,是以和OC一樣使用了比較原始的引用計數的方法來管理記憶體,這裡我們說一下Ref引用計數。

Ref(2.x中為CCObject)是引用計數的模型:

1、Ref抽象類(不能執行個體化,隻能由子類執行個體化);

2、建立預設引用計數為1;// _referenceCount(1);

3、引用計數為0時,執行delete銷毀。

  • retain

    引用計數加1,++_referenceCount

  • release

    引用計數減1,–_referenceCount

  • autorelease

    引用計數不變,放入自動釋放池AutoreleasePool,下一幀調用release

遊戲中,UI節點的釋放取決于引用計數,當節點的_referenceCount為0時則銷毀自身,析構時把它的所有子節點的parent置為nullptr,_children.clear()把子節點的引用計數-1,同時也會把自身的相關Action清除。

在正常情況下,對于節點移除,我們隻關心所需釋放的根節點的引用計數。

如果在節點樹A中,某一節點D我們手動改變引用計數,如retain,則此節點及節點樹分支是不會被釋放的,隻會釋放其它節點分支。如圖,根節點釋放之後,除了D、G、H不會被釋放,其它節點都會被釋放。

Cocos、Lua遊戲記憶體釋放之我見

本文主要講解遊戲邏輯的問題,關于引用計數的問題這裡先說到這,如果有興趣的童鞋可以深入學習,這裡也有一篇文章可以看看。

cocos2dx UI元素記憶體管理----autorelease

在講Lua時候先說下兩個知識點。

1、Lua的gc機制

2、Lua的weak表

  • gc機制

    關于Lua的gc機制,可以解讀源碼lgc.c/lgc.h。或閱讀以下文章做進一步了解。

    Lua Mark-and-Sweep gc

  • weak table

    weak table是一個在meltable中定義了__mode字段的表。

    __mode字段可以取以下三個值:k、v、kv。

    k表示table.key為弱引用,table的keys能夠被自動gc;

    v表示table.value為弱引用,table的values能被自動gc;

    kv就是二者的組合。

    任何情況下,隻要key和value中的一個被gc,那麼這個key-value pair就被從表中移除。
    對于weak table,可以應用到很多地方,現在我們項目中沒有很好利用到它優點,這個要好好檢讨一下。

大家可以思考下

如果把某個table A作為table B的key值後,如果當table A置為nil時,我們希望table B[table A]的值也被删除,應該如何實作?

對于我們項目平時開發邏輯而言,隻需關心下面兩點:

1、無需關心的對象配置設定記憶體

2、将其設定nil時,如果不存在其它引用的情況下,會在gc中被釋放

我們來看一段簡單的代碼,看一下不同情況下值引用gc的效果

collectgarbage("collect")
print("memory count1:", collectgarbage("count"))

local _tbl = {, , }
_tbl[] = "5"    

collectgarbage("collect")
print("memory count2:", collectgarbage("count"))

_tbl[] = nil  
_tbl[] = nil  
_tbl[] = nil  
_tbl[] = nil  
print("memory count3:", collectgarbage("count"))

collectgarbage("collect")
print("memory count4:", collectgarbage("count"))

_tbl = nil
collectgarbage("collect")
print("memory count5:", collectgarbage("count"))

--memory count1:    27.9140625
--memory count2:    27.9580078125
--memory count3:    27.9951171875
--memory count4:    27.9580078125
--memory count5:    27.8095703125
           

上面代碼運作環境是Lua5.3.4,結果在這不做詳細分析。對于1和5的不同,在5.1版本測試是相同的,後續再做考究,這個小測試隻是讓大家大概了解一下什麼情況下table會被gc。

table、function、userdata、thread。對于這幾種值類型,其變量皆為引用類型(變量本身不存儲類型資料,而是指向它們)。對于這幾種值類型的引用gc之後是什麼情況大家可以寫點小demo自行測試一下,順便也可以把弱引用表的gc情況也做個測試。

說了這麼多釋放的原理,歸根結底還是在自己的項目中要妥善運用,才能避免記憶體洩漏以及減少記憶體碎片。

對于我們項目可以注意以下幾點:

1、觀察者模式中的解綁,釋放時要特别注意事件Event、定時器Timer的解綁;

2、輔助工具類盡量不要持有外部執行個體,盡可能做到隻操作,不添加引用;

3、UI界面布局盡量使用視窗基類提供的接口,友善自動釋放。必須清楚幾個視窗基類的原理和使用規則;

4、對自己完成功能子產品的UI層級組成和table表結構非常清晰,確定釋放足夠徹底;

5、對于一定量級的記憶體頻繁申請釋放,可考慮緩存機制(如基于插入移除效率,可使用List代替table),可适用于場景對象,飄血等;

6、深刻了解并利用好weak table,逐漸減去一些強引用表的使用。

繼續閱讀