自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一次C++偽“內存泄漏”的排查之旅

開發(fā) 前端
前段時間做一個需求,需要用到一個本地詞典文件。該詞典原始文件超過2G,在服務啟動的時候加載到內存中,并且保持詞典數(shù)據(jù)的熱加載,也就是不停服更新詞典數(shù)據(jù)到服務進程的內存中。

前段時間做一個需求,需要用到一個本地詞典文件。該詞典原始文件超過2G,在服務啟動的時候加載到內存中,并且保持詞典數(shù)據(jù)的熱加載,也就是不停服更新詞典數(shù)據(jù)到服務進程的內存中。

[[349821]]

之前有同事在其他項目中有熱更新詞典的代碼,我就直接拿來用了。這是典型的雙Buffer詞典。也就是程序運行期間,內存中會同時維持兩份詞典:一份前臺詞典供運行時各處理邏輯檢索,另一份是后臺詞典,在檢測到目標文件修改時(通過檢查文件mtime判斷的是否更新)。在詞典數(shù)據(jù)更新時,重新解析加載,最新的數(shù)據(jù)儲存到后臺詞典中。最后兩個詞典做0 - 1 切換,也就是前臺詞典變后臺詞典,后臺詞典變前臺詞典。

詞典類在服務中采用的核心數(shù)據(jù)結構是unordered_map。前后臺詞典也就是會存在兩個unordered_map。key是某某ID,value是詞典原始文件逐行解析后重組出來的protobuf Message對象。

在線下環(huán)境(非線上生產環(huán)境)測試的時候,自測完代碼邏輯無問題。喵了一眼機器基礎指標,發(fā)現(xiàn)內存會多次上漲。

 

自己畫的:橫軸是時間,縱軸是機器占用內存

 

內存占用在 5-10G之間那次是第一次啟動完成的時間,后面又連續(xù)漲了兩次。懷疑是有內存泄露,在把流量停掉以后,重啟服務。觀測到內存仍舊會規(guī)律上漲,且一個小時會漲一次。如此規(guī)律,讓人不得不懷疑是詞典更新導致。詞典文件是ceph掛載的,會自動更新,所以我?guī)缀鯖]關注過。確認了一下詞典的更新時間和更新頻率。確實也是一小時更新一次,且其每次更新的時間和內存每次上漲時間相match。

想盡快驗證一下是否真的是詞典更新導致的內存上漲,等著詞典一次一次例行更新就太慢了。不過由于這個詞典API判斷詞典是否更新是檢測的文件修改時間(mtime),所以通過touch該詞典文件,可以提前觸發(fā)詞典的加載。

按理說雙buffer的詞典,在正常啟動后暴漲一次內存是合理的。因為啟動的時候內存中加載了詞典的一個版本。一個小時之后詞典更新,第二個版本的詞典數(shù)據(jù)也會加入到內存。而彼時原先的前臺詞典雖然變成了后臺詞典,但是內存并不會立即delete(持有舊詞典數(shù)據(jù)的unordered_map)。因為可能運行的請求處理邏輯仍然會用到舊詞典。

重新閱讀這個詞典API的實現(xiàn)。當內存中存在兩個版本的詞典后,等到詞典第二次更新到時候(也就是第三個版本詞典出現(xiàn)的時候),該實現(xiàn)邏輯是先創(chuàng)建一個詞典對象存儲第三個版本詞典的數(shù)據(jù)。若其加載解析成功則原先的后臺詞典對象就會被delete(第一個版本的詞典占用的內存被釋放)。然后后臺詞典的指針指向剛新建的對象(第三個版本的詞典正式成為后臺詞典),最后做前后臺詞典的切換(第三個版本詞典成為前臺詞典,第二個版本的詞典變成后臺詞典)。

也就是說按照這個詞典API的實現(xiàn)邏輯,內存中確實存在某個時刻存儲著三份詞典的數(shù)據(jù),漲兩次內存也說得通,但是當新的詞典加載完成,上上個版本的詞典對象是會被delete的。所以內存應該回落才對!難道是delete沒有被觸發(fā)嗎?

嘗試了touch了幾次詞典文件發(fā)現(xiàn),確實詞典文件更新會導致內存連續(xù)上漲。但詭異的是后來我嘗試縮減詞典到一個特別小的大小,卻觀察到機器內存并不會下降!哦?這是詞典API本身存在內存泄露的風險嗎?和剛才看代碼時的疑惑一樣,上上版本的詞典沒有觸發(fā)delete?然而通過多次測試又發(fā)現(xiàn)這樣一個事實:

詞典內存不會永遠上漲,啟動完成之后,最多漲兩次,第三次也會漲但比較少,第四次五次更新詞典文件,則幾乎不會導致內存的變化!如果說存在詞典對象沒有被正常delete,那么內存占用應該會繼續(xù)上漲,而不是趨于穩(wěn)定。

頭疼。一方面內存不會無限上漲,不像是內存泄露;但另一方面詞典縮小卻不會導致內存占用減少。

這……讓我在十月的深夜凌亂了。問題又兜回來了嗎?這到底是不是內存泄露?或者到底是不是詞典更新導致的呢?

嘗試了用一些工具來輔助定位是否有內存泄露的風險,但一無所獲。后來注釋掉了每行詞典數(shù)據(jù)重組成pb對象之后insert進unordered_map的代碼,經(jīng)測試詞典更新確實不會再導致內存上漲。說白了實錘了內存上漲就是這兩個前后臺的unordered_map引起的。然而通過加日志也能證實每次舊map對象的delete每次都有被調用到,也就是不存在第三個map對象沒被delete的情況,那么為什么delete掉對象后,其占用的內存無法釋放呢?

遽然陷入絕境,坐困愁城。

突然我靈光一現(xiàn):會不會是glibc導致的持呢?我們都知道內存分配器,比如glibc的ptmalloc,有時候內存分配器的內存管理策略并不一定如我們所愿。

經(jīng)證實確實glibc有這樣的內存分配策略:為了避免大對象頻繁的內存分配和釋放,glibc并不一定會把delete的對象內存立即歸還給操作系統(tǒng),有時候可能繼續(xù)讓進程持有該內存。當后續(xù)再有大對象需要分配的時候,可以直接使用,而不再需要再去向操作系統(tǒng)申請內存。glibc這個策略其實是為了提高內存分配效率的,并且也不會無限占用內存,而是在達到某個平衡點之后內存便不再增長,這也和我所觀察到的現(xiàn)象一致。

說到底這其實不算是一次『內存泄露』。然而這個現(xiàn)象既然不會持續(xù)占用內存,那么到底需不需要解決呢?在我的場景下,答案是肯定的。因為我們的詞典比較大,且不可控,當線上正常服務的時候,內存也會正常上漲,其實是存在OOM風險的。在運行效率和服務穩(wěn)定性之間相比較,自然要讓步于穩(wěn)定性。

那么怎么解決呢?雖然沒有直接搜索到答案,但是直覺告訴我一個更好的內存分配器或許可以解決。死馬當活馬醫(yī),于是我嘗試了讓程序鏈接tcmalloc或jemalloc。最終jemalloc表現(xiàn)良好,可以慢慢釋放掉多余占用的內存。

那些凸起的線是加載和解析詞表的過程中,突然飆上來的內存,但隨機又很快回落,接著慢慢繼續(xù)回落。其實jemalloc在針對大對象存儲時,其性能表現(xiàn)也并不差,甚至使用了jemalloc之后服務一次請求響應的耗時還有不少縮減。

責任編輯:未麗燕 來源: 知乎專欄
相關推薦

2020-08-27 21:36:50

JVM內存泄漏

2022-02-08 17:17:27

內存泄漏排查

2019-02-20 09:29:44

Java內存郵件

2023-01-04 18:32:31

線上服務代碼

2018-09-14 10:48:45

Java內存泄漏

2021-08-19 09:50:53

Java內存泄漏

2011-06-16 09:28:02

C++內存泄漏

2018-07-20 08:44:21

Redis內存排查

2017-01-23 12:40:45

設計演講報表數(shù)據(jù)

2025-03-17 10:01:07

2024-08-19 00:10:00

C++內存

2021-05-13 08:51:20

GC問題排查

2019-03-15 16:20:45

MySQL死鎖排查命令

2021-11-02 07:54:41

內存.NET 系統(tǒng)

2022-09-13 17:46:19

STA模式內存

2021-02-11 14:06:38

Linux內核內存

2014-11-12 13:22:34

2023-04-06 07:53:56

Redis連接問題K8s

2011-06-30 22:23:21

打印機常見問題

2021-11-08 12:44:48

AndroidC++內存
點贊
收藏

51CTO技術棧公眾號