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堆 < 进程堆

这个分析的目的是:

  1. 找出Go进程堆除了Go堆,还包含了哪些内容?
  2. 为什么看到的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

注:

  1. ---p类型的是从背后看但还没映射物理页的区域。
  2. 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内存影响最大的两个因素:

  1. 业务越复杂,代码、常量、全局变量占用的就越多,特别是常量区的.gopclntab增长最为明显。以kubectl为例,42MiB的二进制,这个段占据了大概12MiB。
  2. 业务并发越多,协程越多,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 参考

  1. mmap与brk区别 https://www.cnblogs.com/vinozly/p/5489138.html
  2. Go二进制可视化 https://github.com/goccy/go-graphviz

Go进程内存占用那些事(二)的更多相关文章

  1. Linux系统下输出某进程内存占用信息的c程序实现

    在实际工作中有时需要程序打印出某个进程的内存占用情况以作参考, 下面介绍一种通过Linux下的伪文件系统/proc 计算某进程内存占用的程序实现方法. 首先, 为什么会有所谓的 伪文件 呢. Linu ...

  2. Linux查看进程内存占用及内存使用情况

    LINUX进程内存占用查看方法(1)top可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令:$ top ...

  3. 监控Linux系统所选的服务所占进程内存占用

    [代码] #!/bin/bash #程序功能描述: # 监控系统所选的服务所占进程内存占用 #作者:孤舟点点 #版本:1.0 #创建时间:-- :: PATH=/bin:/sbin:/usr/bin: ...

  4. window 实用操作(结束已打开无法删除进程 内存占用)

    1.win7删除文件,文件夹或文件已在另一程序中打开:https://jingyan.baidu.com/article/e75057f2a41e88ebc91a8985.html 删除文件时,提示“ ...

  5. 查看LINUX进程内存占用情况

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...

  6. 查看LINUX进程内存占用情况(转)

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...

  7. 查看LINUX进程内存占用情况及启动时间

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1) top top命令是Linux下常用的性能分 ...

  8. linux 查看某个进程内存占用情况命令

    1.先用ps查询进程号 ps -aux|grep 进程名字 2.查看更详细的内存占比 cat /proc/3664/status 返回结果:(其中VmRSS为进程所占用的内存)

  9. linux查看进程内存占用

    ps -aux | grep xxx USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 可以看到RSS ...

  10. 使用showmap分析android进程内存占用情况(转载)

    转自:http://my.oschina.net/shaorongjie/blog/105354 可以使用adb shell showmap pid查看一个进程的showmap,这对于我们来说非常有用 ...

随机推荐

  1. QT学习:03 信号与槽

    --- title: framework-cpp-qt-03-信号与槽 EntryName: framework-cpp-qt-03-signal-slot date: 2020-04-09 13:5 ...

  2. 使用Swig转换C++到别的编程语言

    项目github地址: aoce 设定aoce能分别与UE4/Unity3D/android demo对接,就这三来看,分别是C++/C#/java三种语言. C++导出给别的语言使用,一般来说,分为 ...

  3. “科来杯”第九届山东省大学生网络安全技能大赛决赛部分wp

      1.损坏的流量包 wireshark打不开,丢进winhex里,找关键字flag 哎,没找到. 那就仔细看看,在最后发现一串类似base64的密文 base64解密 得到flag 2.签到题 一个 ...

  4. 3568F-麒麟KylinOS国产操作系统演示案例

  5. OPC 详解 第一篇 基础概念

    一 .概述 OPC 的全称是OPC(OLE for Process Control), 用于过程控制的OLE,OLE(Object Linking and Embedding)大家都知道是对象连接与嵌 ...

  6. 面试官:Java类是如何被加载到内存中的?

    面试连环call Java类是如何被加载到内存中的? Java类的生命周期都有哪些阶段? JVM加载的class文件都有哪些来源? JVM在加载class文件时,何时判断class文件的格式是否符合要 ...

  7. python学习_PIL的Image模块初步使用

    基本介绍: Pillow 是 Python 中较为基础的图像处理库,主要用于图像的基本处理,比如裁剪图像.调整图像大小和图像颜色处理等.与 Pillow 相比,OpenCV 和 Scikit-imag ...

  8. 微信小程序车牌键盘输入组件(支持单个删除更改,支持赋值,支持新能源)

    网上一搜一大堆类似但大多都相对简单,适用的场景并不多.多数也不支持赋值 不支持单个删除更改 我就借鉴了以下文章的思路,为了达到自己想要的效果做了相对应的更改. 借鉴文章链接:> https:// ...

  9. Nginx 可视化配置神器NginxConfig

    Nginx 是前后端开发工程师必须掌握的神器.该神器有很多使用场景:比如反向代理.负载均衡.动静分离.跨域等等. 把 Nginx 下载下来打开 conf 文件夹的 nginx.conf 文件,Ngin ...

  10. oeasy教您玩转vim - 61- # 编辑过程

    ​ 编辑过程 回忆上次 vi可以加各种参数 vi +4 oeasy.txt vi +/shiyanlou vi +%s/shiyanlou/oeasy/g oeasy.txt vi可以接收stdin的 ...