上一篇文章找同事review了一下,收到的反饋是鋪墊太長了,我儘可能直入正題,哈哈html
最近dbd壓測時發現內存泄漏,其實這個問題去年已經暴露了,參見這篇博客【壓測周】。當時排查不夠仔細,在此反省下。關於dbd的內存問題,還有這篇博客討論線程安全,以及這篇博客討論臨時變量的處理。當時還存了一個尾巴,由於用到關聯數組進行髒數據管理,這部分數據是怎麼釋放的一直沒搞明白。今天算是搞明白了,這個內存泄漏的bug也源於此。golang
基於引用計數的gc機制,應該是在引用計數爲0的時候釋放內存的,lpc也基本沒有例外。具體詳情能夠參看free_svalue的實現,裏面基本是引用計數減去1,而後檢查是否引用計數爲0,如果0就執行相關的Free操做。其中,有一半的篇幅都是講字符串的釋放的,剩下的則是object、buffer、array、mapping、class以及function的釋放問題。因此,基本上,lpc不須要像golang同樣,中止一切操做作mark and swap的gc操做。也不一樣於Lua的標記清除,不須要分步執行,只要引用計數爲0,立馬釋放掉。數組
爲何是基本上呢?由於對於object類型,lpc並無對其當即釋放,而是放到一個鏈表裏面,待主循環裏沒有函數執行時,再行銷燬。這樣作,一方面是爲了配合複雜的熱更新機制,另外一方面,則是解決object循環引用的須要。對於對象的銷燬,首先調用的是destruct_object, 這個函數最終的做用是爲對象打上銷燬標誌。在這個函數裏,針對master object和simul_efun object作了熱更新的處理。而後從棧上移除object的全部引用,從object#n列表裏移除這個object。最後打上銷燬標記,並放入待銷燬列表。安全
下一步,則調用destruct2, 首先將該object的全部變量釋放。若是該對象有對自身引用,則由於這一步而引用計數減一,方便接下來釋放該對象自己。接着調用free_object, 對象引用次數爲0,已打上銷燬標記,功成身退,成功析構。app
因爲在dbd中,沒有跑lpc腳本,全部的關聯數組(mapping)都是在C層生成和使用的,所以引用計數須要手工維護,這就是萬惡之源了。對於腳本層,新建的變量,引用計數都應該爲1,退出變量做用域時,則引用計數減一。放入mapping時,引用次數相應加1。而在C層使用mapping時,全部變量的組織都是圍繞mapping展開的,變量的生命週期就等同於mapping的生命週期。當序列化數據後,釋放數據對應的mapping時,會對其中的全部變量引用計數-1,但依然計數不爲0,所以沒法釋放。函數
解決的辦法也至關簡單,變量放入mapping前,先free_svalue,將引用計數變爲0,則加入mapping後,引用計數爲1,生命週期就同mapping同樣了。線程