出于不少目的,我从最新的 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.go 的 mallocinit()
。blog
我不得不说,这个比我最初觉得地更有意思也更有教育意义,尽管这意味着查看 ps
再也不是一个检测你的 Go 程序中内存泄露的好方法(舒适提示,我不肯定它曾经是否是)。结论是,检测这类内存使用最好的方法是同时使用 runtime.ReadMemStats()
(能够经过 net/http/pprof 暴露出去)和 Linux 的 smem
程序或者养成对有意义的内存地址空间占用生成详细信息的习惯。进程
PS: Unix 一般足够智能,能够理解 PROT_NONE
映射不会耗尽内存,所以不该该对系统内存过量使用的限制进行统计。然而,它们会统计每个进程的总地址空间进行统计,这意味着你运行 1.4 的 Go 程序时不能真的使用这么多。因为总内存地址空间的最大数几乎不会达到,所以这彷佛不是一个问题。内存
全部的信息都在 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