爲何你的 64-bit 程序可能佔用巨大的虛擬空間

出於不少目的,我從最新的 Go 系統內核開發源碼複製了一份代碼,在一個正常的運行環境中構建(和從新構建)它,在構建版本基礎上週期性地從新構建 Go 程序。近期我在用 ps 查看個人一個程序的內存使用狀況時,發現它佔用了約 138 GB 的巨大虛擬空間(Linux ps 命令結果的 VSZ 字段),儘管它的常駐內存還不是很大。某個進程的常駐內存很小,可是須要內存很大,一般是表示有內存泄露,所以我內心一顫。linux

(用以前版本的 Go 構建後,根據運行時間長短不一樣,一般會有 32 到 128 MB 不一樣大小的虛擬內存佔用,比最新版本小不少。)git

還好這不是內存泄漏。事實上,以後的實驗代表即便是個簡單的 hello world 程序也會有佔用很大的虛擬內存。經過查看進程的 /proc//smaps 文件((cf)能夠發現幾乎全部的虛擬空間是由兩個不可訪問的 map 佔用的,一個佔用了約 8 GB,另外一個約 128 GB。這些 map 沒有可訪問權限(它們取消了讀、寫和可執行權限),因此它們的所有工做就是專門爲地址空間預留的(甚至沒有用任何實際的 RAM)。大量的地址空間。github

這就是如今的 Go 在 64 位系統上的低級內存管理的工做機制。簡而言之,Go (理論上)從連續的 arena 區域上進行低級內存分配,申請 8 KB 的頁;哪些頁能夠無限申請存儲在一個巨大的 bitmap。在 64 位機器上,Go 會把所有的內存地址空間預留給 bitmap 和 arena 區域自己。程序運行時,當你的 Go 程序真正使用內存時,arena bitmap 和內存 arena 片斷會從簡單的預留地址空間變爲由 RAM 備份的內存,供其餘部分使用。golang

(bitmap 和 arena 一般是經過給 mmap 傳入 PROT_NONE 參數進行初始化的。當內存被使用時,會使用 PROT_READ|PROT_WRITE 從新映射。當釋放時,我不肯定它作了什麼,因此對此我不發表意見。)spa

這個例子是用當前發佈的 Go 1.4 開發版本復現的。以前的版本的 64 位程序運行時會佔用更小的須要空間,雖然讀 Go 1.4 源碼時我也沒找到緣由。操作系統

以個人理解,一個有意思的影響是 64 位 Go 程序的大部份內存分配均可能佔用至多 128 GB 的空間(也可能在整個運行週期內全部的內存分配都會,我不肯定)。code

瞭解更多細節,請看 src/runtime/malloc2.go 的註釋和 src/runtime/malloc1.gomallocinit()blog

我不得不說,這個比我最初覺得地更有意思也更有教育意義,儘管這意味着查看 ps 再也不是一個檢測你的 Go 程序中內存泄露的好方法(舒適提示,我不肯定它曾經是否是)。結論是,檢測這類內存使用最好的方法是同時使用 runtime.ReadMemStats()(能夠經過 net/http/pprof 暴露出去)和 Linux 的 smem 程序或者養成對有意義的內存地址空間佔用生成詳細信息的習慣。進程

PS: Unix 一般足夠智能,能夠理解 PROT_NONE 映射不會耗盡內存,所以不該該對系統內存過量使用的限制進行統計。然而,它們會統計每個進程的總地址空間進行統計,這意味着你運行 1.4 的 Go 程序時不能真的使用這麼多。因爲總內存地址空間的最大數幾乎不會達到,所以這彷佛不是一個問題。內存

附錄:在 32 位系統上是怎樣的

全部的信息都在 mallocinit() 註釋中。簡而言之,就是運行時預留了足夠大的 arena 來處理 2 GB 的內存(「僅」佔用 256 MB)可是僅預留 2 GB 中理論上它可使用的 512 MB 地址空間。若是後續的運行過程當中須要更多內存,就向操做系統申請另外一個塊的地址空間,優先 arena 區域剩下的 1.5 GB 的地址空間中分配。大多數狀況下,運行的程序都會正常申請到須要分配的空間。


via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoBigVirtualSize

做者:ChrisSiebenmann 譯者:lxbwolf 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

相關文章
相關標籤/搜索