1. 程式人生 > >Lua中記憶體管理和釋放的理解

Lua中記憶體管理和釋放的理解

Lua記憶體是自動收集的, 這點跟Java類似, 不被任何物件或全域性變數引用的資料,將被首先標記為回收,不需要開發者做任何事情.但是,正如Java也會有記憶體洩露一樣, Lua也會有, 只不過,跟C++的不同,它是由於程式碼執行所裝載的資源,並沒有被徹底銷燬而導致,其中,最臭名昭著的就是不小心把區域性變數宣告成了全域性變數(忘了加local修飾符)。 類似這樣造成的記憶體洩露, 跟任何其他語言的記憶體洩露一樣,容易產生,卻難以察覺, 給開發的應用帶來潛在的很大隱患.

那麼, 有沒有一些有效的解決辦法, 來解決這個這個隱患呢, 答案就是collectgarbage. collectgarbage就是開放給Lua開發人員, 用於監聽Lua的記憶體使用情況(collectgarbage(“count”)), 同時,它還提供了collectgarbage(“collect”),允許在適當的時候進行顯式的回收.

現在,通過測試程式碼來看看,如何玩轉collectgarbage.

首先,為了有明顯的對比, 先來看沒有產生洩露的情況, 執行以下的test1(程式碼如下):

function test1( ... )
  collectgarbage("collect");--為了有乾淨的環境,先把可以收集的其他垃圾趕走先
  local  c1 = collectgarbage("count")
  print("最開始,Lua的記憶體為",c1)
  local colen = {}--在這裡,colen是本地變數
  print("現在,宣告5000個數組,並加到colen中")
  for i=1
,50000 do table.insert(colen,{}) --不斷的把新建立的陣列,放到一個區域性的變數中 end local c2 = collectgarbage("count") print("現在記憶體為:",c2) end test1()

執行結果如下:
最開始,Lua的記憶體為 23.765625
現在,宣告5000個數組,並加到colen中
現在記憶體為: 3782.2734375
[Finished in 0.1s]

這裡看到, 被local 宣告的colen加了5000陣列, test1呼叫後, 記憶體增加了大概300K(25906K-25620K).
現在,我們來做記憶體回收(呼叫mem函式, 程式碼如下):

function mem( )
    print("呼叫GC收集..")
    local c2 = collectgarbage("count")
    collectgarbage("collect")
    print("收集後,當前的Lua記憶體為",c2)
end

執行結果如下:
@@@@@呼叫test1
最開始,Lua的記憶體為 24.5205078125
現在,宣告5000個數組,並加到colen中
現在記憶體為: 3783.0283203125
@@@@@呼叫test1完畢
呼叫GC收集..
收集後,當前的Lua記憶體為 3783.201171875
呼叫GC收集..
收集後,當前的Lua記憶體為 24.7705078125
呼叫GC收集..
收集後,當前的Lua記憶體為 24.76953125
呼叫GC收集..
收集後,當前的Lua記憶體為 24.767578125
[Finished in 0.1s]

( 為了保證記憶體的穩定,以上注意mem被呼叫了多次, 再第2次, 可以看到記憶體開始下降, 最後,大概在24.767578125K穩定下來)
好了, 從最初的3783.201171875K, 到回收後的24.767578125K, 呼叫多次之後,兩者並沒有發生變化, 也就是說,函式test1的執行,並沒有產生無法回收的記憶體,沒有洩露出現.

好了,現在執行有洩露的test2(程式碼如下), test2跟test1相比,只有一處不同:就是colen被誤宣告為全域性:

function test2( ... )
  collectgarbage("collect");--為了有乾淨的環境,先把可以收集的其他垃圾趕走先
  local  c1 = collectgarbage("count")
  print("最開始,Lua的記憶體為",c1)
  colen = {}--在這裡,colen是本地變數
  print("現在,宣告5000個數組,並加到colen中")
  for i=1,50000 do
    table.insert(colen,{}) --不斷的把新建立的陣列,放到一個全域性的變數中
  end
  local c2 = collectgarbage("count")
  print("現在記憶體為:",c2)
end
print("@@@@@test2")
test2()
print("@@@@@test2完畢")

執行結果如下
@@@@@test2
最開始,Lua的記憶體為 24.4541015625
現在,宣告5000個數組,並加到colen中
現在記憶體為: 3782.9619140625
@@@@@test2完畢
[Finished in 0.1s]
也就是說,記憶體也在3782.9619140625,跟test1幾乎是相等, 好了,現在再呼叫回收(mem)函式,產生結果如下:

呼叫GC收集..
收集後,當前的Lua記憶體為 3783.197265625
呼叫GC收集..
收集後,當前的Lua記憶體為 3783.1962890625
呼叫GC收集..
收集後,當前的Lua記憶體為 3783.197265625
呼叫GC收集..
收集後,當前的Lua記憶體為 3783.1962890625

為了保證函式回收被執行,這次,總共呼叫了4次mem函式(看以上列印行數), 那麼,從上面的結果我們看, 很不幸, 從第1次,到最後第4次, 記憶體都還是穩定在3783.左右, 也就是說, 跟呼叫test2前相比,即使Lua進行了記憶體回收, 記憶體卻不會將下來 看來, 這幾千K記憶體, 由於已放到了全域性函式中,是永遠沒有機會被回收到了!

總結一: 如何監測Lua的程式設計產生記憶體洩露:

  1. 針對會產生洩露的函式,先呼叫collectgarbage(“count”),取得最初的記憶體使用

  2. 函式呼叫後, collectgarbage(“collect”)進行收集, 並使用collectgarbage(“count”)再取得當前記憶體, 最後記錄兩次的使用差

  3. 從test1的收集可看到, collectgarbage(“collect”)被呼叫,並不保證一次成功, 所以, 大可以呼叫多次

總結二: 如何避免Lua應用中出現的記憶體使用過大行為:

  1. 當然是程式碼實現不出現洩露, ,避免使用全域性變數,因為存在全域性變數可能導致多執行緒不安全。在建立多個lua虛擬機器的時候會2個執行緒同時操作一個變數。這時候就會出現問題

  2. 在測試中,其實還發現, Lua中被分配的記憶體,其實並不會自動回收(個人估計要麼就是Lua虛擬機器沒有做這個事情,要麼就是回收的時機是在C層), 所以, 為了避免記憶體過大, 應用的執行時,可能需要定期的(呼叫collectgarbage(“collect”),又或者collectgarbage(“step”))進行顯式回收。

借鑑連結:http://blog.csdn.net/henren555/article/details/17579153