Go进程内存占用那些事(二)
0x01 最简单的Go程序
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("hello")
for {
// sleep一下,避免CPU占用过高。
time.Sleep(1 * time.Second)
}
}
不含调试符号的二进制大小为1229464Byte,约1.2MiB。
编译出二进制,同样通过readelf工具检查下。看到Program Header中有3个可加载段。
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0815e7 0x0815e7 R E 0x1000
LOAD 0x082000 0x0000000000482000 0x0000000000482000 0x091cd0 0x091cd0 R 0x1000
LOAD 0x114000 0x0000000000514000 0x0000000000514000 0x017ea0 0x0497f0 RW 0x1000
根据Flg可以判断分别对应代码、常量、变量。内存大小分别为:517.47KiB、583.20KiB、293.98KiB。
大约是1.2MiB。
Go的堆和通常意义的进程堆不是等同的。大小关系为
Go堆 < 进程堆
这个分析的目的是:
- 找出Go进程堆除了Go堆,还包含了哪些内容?
- 为什么看到的Go进程的RSS值,远大于Go的堆?
0x02 详细分析
再通过readelf -W -S main查看,可以确认变量区保存了以下section
类型段见文档elf中解释的。
PROGRBITS:指完全由elf文件中的内容定义。
NOBITS:不占用文件的实际空间,但有真实的偏移。告诉加载器,不用真的去读文件内容。
| Section | 大小(Bytes) | 类型 | 说明 |
|---|---|---|---|
| .go.buildinfo | 224 | PROGBITS | Go的构建信息 |
| .noptrdata | 67104 | PROGBITS | 非指针类型数据,大约65KiB |
| .data | 30608 | PROGBITS | 可能是指针类型的数据,大约29KiB |
| .bss | 183KiB | NOBITS | 未初始化全局静态,猜测是给Go runtime使用C栈部分使用的。 |
| .noptrbss | 14KiB | NOBITS |
除了C语言已有段,Go还有自己的自定义Section如下。
都包含在了常量段中。
| 段名 | 解释 | 备注 |
|---|---|---|
| .typelink | 类型链接段 | modules中记录在types类型表中的偏移,或者是typemap中的key |
| .itablink | 接口链接段 | 大小跟接口数量有关 |
| .gosymtab | 符号段 | strip后,这个段为空 |
| .gopclntab | Go中用于栈回溯用到的pc和栈帧映射表,每个module有自己的表 | 程序代码越多,此段越大。 |
go中的module并不是package。go支持plugin,所以一般编译出来的程序只有一个module。
readelf看到的可加载的Section有12个,但查看main的/proc/<pid>/smap可以看到多达22个Section。
查看smap中的段
| 段 | 用途 | 来源 | 权限 | 对应Section |
|---|---|---|---|---|
| 00400000-00482000 | 代码段 | 二进制中的代码 | r-xp | |
| 00482000-00514000 | 常量段 | 二进制中的 | r--p | |
| 00514000-0052c000 | 变量段 | 二进制中 | rw-p | .go.buildinfo .noptrdata .bss 一段 |
| 0052c000-0055e000 | 变量段 | 二进制中 | rw-p | .bss一段和noptrbss |
| c000000000-c000400000 | 根据寄存器的内容看是栈 | rw-p | ||
| c000400000-c004000000 | 也是栈。 | ---p | ||
| 7fcce0548000-7fcce2800000 | mspan | |||
| 7fcce2800000-7fcce2c00000 | mspan | rw-p | ||
| 7fcce2c00000-7fcce2c04000 | mspan | rw-p | ||
| 7fcce2c04000-7fccf317d000 | mspan | ---p | ||
| 7fccf317d000-7fccf317e000 | mspan | rw-p | ||
| 7fccf317e000-7fcd0502d000 | mspan | ---p | ||
| 7fcd0502d000-7fcd0502e000 | mspan | rw-p | ||
| 7fcd0502e000-7fcd07403000 | mspan | rw-p | ||
| 7fcd07403000-7fcd07404000 | mspan | rw-p | ||
| 7fcd07404000-7fcd0787d000 | mspan | ---p | ||
| 7fcd0787d000-7fcd0787e000 | mspan | rw-p | ||
| 7fcd0787e000-7fcd078fd000 | mspan | ---p | ||
| 7fcd078fd000-7fcd0795d000 | mspan | rw-p | ||
| 7ffdd88fd000-7ffdd891f000 | C栈和system栈 | rw-p | ||
| 7ffdd898c000-7ffdd8990000 | vvar | r--p | ||
| 7ffdd8990000-7ffdd8992000 | vdso | r-xp |
注:
- ---p类型的是从背后看但还没映射物理页的区域。
- mspan为笔者加的,实际看smaps内容,没有这样的标识。笔者通过dlv等手段判定的。mspan是Go中管理Go堆的数据结构。
Q:为什么mspan和堆区隔了比较大的空间?
A:mspan是通过指定mmap flag为 mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)分配的。而堆区有严格的地址空间约束,是指定起始地址通过mmap向操作系统主动占用的。这样做的好处是可以很方便通过地址判断属于哪个内存区域。
待分配的空间通过runtime.mheap_.arenaHints(单向链表)管理的。1.20之后增加了userArena。通过dlv调试,可以确认c000000000-c000400000栈段是通过runtime.sysAlloc分配的,即指定起始地址的mmap方式分配的。分配之后就会从runtime.mheap_.arenaHints中移除掉。
通过dlv中看到的内容如下:
(dlv) p %x runtime.mheap_.arenaHints
*runtime.arenaHint {
_: runtime/internal/sys.NotInHeap {
_: runtime/internal/sys.nih {},},
addr: c004000000,
down: %!x(bool=false),
next: *runtime.arenaHint {
_: (*"runtime/internal/sys.NotInHeap")(0x7fcd079219c8),
addr: 1c000000000,
down: %!x(bool=false),
next: *(*runtime.arenaHint)(0x7fcd079219b0),},}
§ 0x03 小结
通过以上分析得到的Go进程的内存RSS占用组成如下:

总体来说,对Go内存影响最大的两个因素:
- 业务越复杂,代码、常量、全局变量占用的就越多,特别是常量区的
.gopclntab增长最为明显。以kubectl为例,42MiB的二进制,这个段占据了大概12MiB。 - 业务并发越多,协程越多,Go堆的占用就越多。因为Go堆包含了用户协程栈。
§ 0x04 为什么Go进程RSS高?
另外,因为用Go开发大多是一些容器应用,要求这些程序静态编译,结果就是Go的代码段不可能与其他Go应用复用,所以Go的RSS会较高。
还有,因为Go自己实现了栈管理,所以回溯栈(打堆栈、GC场景)时需要依赖pclntab,这部分信息如上所述,也占据很大内存空间,同样不同Go程序也不能复用,所以更加剧了Go的内存占用。
从上述两个因素出发,如何优化内存占用呢?
4.1 减小代码段
动态编译后面社区大概率是不会支持了,见issue。所以想通过这种方式减少Go的代码段,不可行。
Go相比Python这种脚本语言有个问题,一个package下的源文件,即便里面的某个接口你用不到,只要你用到了其中一个,其他的源文件就被编译进二进制中。
如果可以识别哪部分占比大,同时又不用的话,就可以这样本地化之后,通过//go:build的构建标签来优化。查看二进制中哪个package贡献大小,可以使用如下的工具查看: https://github.com/goccy/go-graphviz
4.2 GC调优
另外一个思路就是GC调优。
虽然GO只有一个GOGC的调优参数,但关联较多,需要额外一个文档才能说清楚。
§ 0x05 参考
- mmap与brk区别 https://www.cnblogs.com/vinozly/p/5489138.html
- Go二进制可视化 https://github.com/goccy/go-graphviz
Go进程内存占用那些事(二)的更多相关文章
- Linux系统下输出某进程内存占用信息的c程序实现
在实际工作中有时需要程序打印出某个进程的内存占用情况以作参考, 下面介绍一种通过Linux下的伪文件系统/proc 计算某进程内存占用的程序实现方法. 首先, 为什么会有所谓的 伪文件 呢. Linu ...
- Linux查看进程内存占用及内存使用情况
LINUX进程内存占用查看方法(1)top可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令:$ top ...
- 监控Linux系统所选的服务所占进程内存占用
[代码] #!/bin/bash #程序功能描述: # 监控系统所选的服务所占进程内存占用 #作者:孤舟点点 #版本:1.0 #创建时间:-- :: PATH=/bin:/sbin:/usr/bin: ...
- window 实用操作(结束已打开无法删除进程 内存占用)
1.win7删除文件,文件夹或文件已在另一程序中打开:https://jingyan.baidu.com/article/e75057f2a41e88ebc91a8985.html 删除文件时,提示“ ...
- 查看LINUX进程内存占用情况
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- 查看LINUX进程内存占用情况(转)
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- 查看LINUX进程内存占用情况及启动时间
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1) top top命令是Linux下常用的性能分 ...
- linux 查看某个进程内存占用情况命令
1.先用ps查询进程号 ps -aux|grep 进程名字 2.查看更详细的内存占比 cat /proc/3664/status 返回结果:(其中VmRSS为进程所占用的内存)
- linux查看进程内存占用
ps -aux | grep xxx USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 可以看到RSS ...
- 使用showmap分析android进程内存占用情况(转载)
转自:http://my.oschina.net/shaorongjie/blog/105354 可以使用adb shell showmap pid查看一个进程的showmap,这对于我们来说非常有用 ...
随机推荐
- dotnet 融合 Avalonia 和 UNO 框架
现在在 .NET 系列里面,势头比较猛的 UI 框架中,就包括了 Avalonia 和 UNO 框架.本文将告诉大家如何尝试在一个解决方案里面融合 Avalonia 和 UNO 两个框架,即在一个进程 ...
- Stable Diffusion 生成个性图片指南
在当今人工智能领域,midjourney无疑是生成图片的王者,但是苦于付费才能使用,今天我就给大家分享一下midjourney平替stable diffusion,实现本地生成不逊色于midjourn ...
- hive第二课:Hive3.1.2概述与基本操作(修改版)
Hive3.1.2概述与基本操作 1.Hive基本概念 1.1 Hive简介 Hive本质是将SQL转换为MapReduce的任务进行运算,底层由HDFS来提供数据存储,说白了hive可以理解为一个将 ...
- 开源一个RAG大模型本地知识库问答机器人
弹指间,2009年大学毕业到现在2024年,已经15年过去了. 前2天,看到自己14年在博客园写的一个博客,哪个时候是工作之余创业 感兴趣的朋友可以看看我10年前发的一篇博客https://www.c ...
- 【简单总结】SLAM 算法的 Benchmark 及相关数据集的结果对比
前言与参考 主要是copy一下总结,方便自己后续找方案特定使用,所有的出处均在标题处和原链接跳转,此处仅做各个benchmark收集使用,如果有原作者觉得侵权,请联系我 将全力配合相关内容和链接删除 ...
- C# 温故知新 第三篇 C# 编程概念 之程序集
在微软C# 官方开发指南中,介绍到在C# 开发中设计到这些 编程概念 当然包括不限于这些: 程序集:程序集构成了 .NET 应用程序的部署.版本控制.重用.激活范围和安全权限的基本单元. 程序集是为协 ...
- Spark内核架构核心组件.txt
1.Application2.spark-submit3.Driver4.SparkContext5.Master6.Worker7.Executor8.Job9.DAGScheduler10.Tas ...
- CF1137C 题解
考虑把每个点进行拆成 \(d\) 个点表示星期几走到这个点,那么原图上的边 \((u,v)\) 就被拆成\((pos_{u,i},pos_{v,i+1})\) 表示星期的变化. 然后考虑进行缩点,在一 ...
- Python pip 切换为国内镜像源
参考文章:https://codeplayer.vip/p/j7tmc [windows] 备份记录指令:(永久全局设置pypi国内镜像源地址) 1 pip config --global set g ...
- workman的工作流程
workerman有两种进程模型1.基本的master worker模型2.master gateway worker模型 master worker模型工作流程及进程间关系如下: master wo ...