为何你的 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语言中文网 荣誉推出

相关文章
相关标签/搜索