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,这对于我们来说非常有用 ...
随机推荐
- lumen、laravel 环境问题汇总
框架报500 1.chmod 777 -R storage 将日志目录权限设置下. 2.修改fastcgi,将代码目录包含进去. fastcgi_param PHP_ADMIN_VALUE " ...
- 嵌入式必读!瑞芯微RK3568J/RK3568B2开发板规格书
评估板简介 创龙科技TL3568-EVM是一款基于瑞芯微RK3568J/RK3568B2处理器设计的四核ARM Cortex-A55国产工业评估板,每核主频高达1.8GHz/2.0GHz,由核心板和评 ...
- Spring手动获取bean的四种方式
一.实现BeanFactoryPostProcessor接口 @Component public class SpringUtil implements BeanFactoryPostProcesso ...
- Microsoft Compatibility telemetry占cpu资源高
1.在Windows10系统卡的时候,打开任务管理器,发现Microsoft Compatibility telemetry占用了大量的系统资源,特别是CPU占用率非常高. 位置:控制面板->管 ...
- Spring DI(依赖注入)自动装配 @Autowired、@Resource注解
@Autowired:一部分功能是查找实例,从Spring容器中根据类型(Java类)获取对应的实例:另一部分功能就是赋值,将找到的实例,装配给另一个实例的属性值.(注:一个Java类型在同一个Spr ...
- 记录荒废了三年的四年.net开发的第一次面试
对象 身在成都小微企业,前两天面试深圳老牌金蝶公司.对我这个荒废了三年光影的人来说,怎一个跨度之大了得?作为人我生第一次面试的,整个面试过程,只能用诡异来形容这次感受.而结尾也是迷迷糊糊中草草收场. ...
- useCookie函数:管理SSR环境下的Cookie
title: useCookie函数:管理SSR环境下的Cookie date: 2024/7/13 updated: 2024/7/13 author: cmdragon excerpt: 摘要:本 ...
- [oeasy]python0051_ 转义_escape_字符_character_单引号_双引号_反引号_ 退格键
转义字符 回忆上次内容 上次研究的是进制转化 10进制可以转化为其他形式 bin oct hex 其他进制也可以转化为10进制 int 可以设置base来决定转为多少进制 回忆一下 我们为什么会有八进 ...
- oeasy教您玩转vim - 59 - # 编辑总结
[Github地址] (https://github.com/overmind1980/oeasyvim) [Gitee地址] (overmind1980/oeasyvim) [蓝桥实验楼 邀请码 ...
- spring boot 快速入门(一)创建一个简单的Spring Boot项目
1.什么是Spring Boot Spring Boot makes it easy to create stand-alone, production-grade Spring based Appl ...