從事 C 和 C++ 的開發,有時候會碰到內存異常飈高不下降的問題。

此時,你可能碰到了內存泄漏。

遇上內存泄漏,不要驚慌,掌握到了訣竅,就可以輕鬆的解決問題,逢凶化吉。

首先,我們是To B 應用,涉及繁忙的 IO 壓力,不能依靠重啟服務來解決問題。

重啟可能會隱藏問題,導致失去了抓住問題的線索。正確的解法是,不要對自己過度自信,認為自己的代碼不可能有內存泄漏,對內存的分配釋放,保持一顆敬畏之心。

心態上有準備之後,就走出了第一步:未雨綢繆。

未雨綢繆

解決線上的內存泄漏,當前最靠譜,最好用的就是 gperftools 自帶的 heap profiler。學會它,並把它集成到自己的服務里,可以靈活的 enable 和 disable。

注意:enable 了 heap profiler 之後,可能給 IO 增加延遲。所以,在完成之後,需要 disable,別忘記了。

宏觀分析內存泄漏的規律

複雜的內存泄漏,一般是發生在一個很特殊的路徑上,泄漏量不是很大,所以需要獲取到泄漏的規律。可能的做法有:

  • 企業服務可能自帶監控數據,分析監控內存佔用規律;
  • 寫一個腳本,放到後台,記錄內存的佔用以及時間,定期檢查;

抓取內存分配的分布

如果能確認有內存泄漏,那麼此時可以開始啟用 heap profiler。在連續抓取一段時間的 heap 分配與釋放之後,通過 pprof 工具,來得到一段時間內的內存泄漏分布。pprof 支持 text、pdf 等文本輸出。

註:pprof 需要被觀察進程的符號表。低版本的pprof 工具查找 debuginfo 有問題,需要使用最新版。

分析 heap profiler 結果

heap profiler 會輸出函數泄漏的內存量。

一般如果是簡單的 new 之後,沒有 delete,這種泄漏最容易發現。真實場景可能比這複雜得多。有時候定位了相應的函數,但是代碼比較複雜,還是找不到泄漏點,可以參考如下幾個地方:

  • map:c++的map,在下標訪問的時候自動構造 value 對象,可能造成 map 無限增長;
  • unordered_set: 在插入大量的元素之後,再刪除,內存佔用保持不變,需要手動 rehash;
  • 容器的 size 很大:通過 gcore -o xxx `pidof yyy`,然後 gdb 去查看有嫌疑的容器的長度;
  • 如果容器的 size 正常,但是還是有泄漏,可能跟智能指針有關,例如 shared ptr,被泄漏;

使用 gcore 的好處是可以 dump 出當前服務的內存鏡像,方便後期 gdb 進一步分析。

而不是直接 gdb attach 到正在運行的進程,這會導致嚴重的問題。

總結

通過以上的操作,抽絲剝繭,內存泄漏最終無所遁形。

我們也需要有一個很好的編程習慣,在源頭避免內存泄漏,防止內存泄漏的出現。

克服內存泄漏的方法:

  • 不使用 new delete,而是 unique_ptr;
  • 利用 RAII,封裝一些資源的佔用與釋放;
  • 正確使用 shared_ptr;

推薦閱讀:

相关文章