偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一次C++偽“內(nèi)存泄漏”的排查之旅

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

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

[[349821]]

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

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

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

 

自己畫的:橫軸是時(shí)間,縱軸是機(jī)器占用內(nèi)存

 

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

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

按理說雙buffer的詞典,在正常啟動(dòng)后暴漲一次內(nèi)存是合理的。因?yàn)閱?dòng)的時(shí)候內(nèi)存中加載了詞典的一個(gè)版本。一個(gè)小時(shí)之后詞典更新,第二個(gè)版本的詞典數(shù)據(jù)也會(huì)加入到內(nèi)存。而彼時(shí)原先的前臺(tái)詞典雖然變成了后臺(tái)詞典,但是內(nèi)存并不會(huì)立即delete(持有舊詞典數(shù)據(jù)的unordered_map)。因?yàn)榭赡苓\(yùn)行的請求處理邏輯仍然會(huì)用到舊詞典。

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

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

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

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

頭疼。一方面內(nèi)存不會(huì)無限上漲,不像是內(nèi)存泄露;但另一方面詞典縮小卻不會(huì)導(dǎo)致內(nèi)存占用減少。

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

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

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

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

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

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

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

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

責(zé)任編輯:未麗燕 來源: 知乎專欄
相關(guān)推薦

2022-02-08 17:17:27

內(nèi)存泄漏排查

2020-08-27 21:36:50

JVM內(nèi)存泄漏

2019-02-20 09:29:44

Java內(nèi)存郵件

2023-01-04 18:32:31

線上服務(wù)代碼

2018-09-14 10:48:45

Java內(nèi)存泄漏

2021-08-19 09:50:53

Java內(nèi)存泄漏

2011-06-16 09:28:02

C++內(nèi)存泄漏

2017-01-23 12:40:45

設(shè)計(jì)演講報(bào)表數(shù)據(jù)

2018-07-20 08:44:21

Redis內(nèi)存排查

2025-03-17 10:01:07

2024-08-19 00:10:00

C++內(nèi)存

2021-05-13 08:51:20

GC問題排查

2021-11-02 07:54:41

內(nèi)存.NET 系統(tǒng)

2019-03-15 16:20:45

MySQL死鎖排查命令

2022-09-13 17:46:19

STA模式內(nèi)存

2021-02-11 14:06:38

Linux內(nèi)核內(nèi)存

2014-11-12 13:22:34

2023-04-06 07:53:56

Redis連接問題K8s

2011-06-30 22:23:21

打印機(jī)常見問題

2021-11-08 12:44:48

AndroidC++內(nèi)存
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號