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. 高通 LK阶段配置使用I2C-8

    以MSM8953为例. 原文(有删改):https://blog.csdn.net/qq_29890089/article/details/108294710 项目场景 因为项目需要,需要在高通MSM ...

  2. 理解Node.js 的重要概念

    Node.js是什么 Node.js是JavaScript的运行时(runtime),终于脱离了浏览器也能运行JavasScript了.同时,Node.js又暴露fs,http等对象给JS,使JS能够 ...

  3. Linux 更新网络时间

    下载包 yum install -y ntpdate 同步网络时间 ntpdate 0.asia.pool.ntp.org 若上面的时间服务器不可用,也可以改用如下服务器进行同步: time.nist ...

  4. 搜索Python编程获取相关图书信息

    1.获取相关图书信息 #搜索"Python编程"获取相关图书信息 from selenium import webdriver from selenium.webdriver.su ...

  5. ComfyUI进阶:Comfyroll插件 (四)

    ComfyUI进阶:Comfyroll插件 (四) 前言: 学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业 ...

  6. 网络基础 Modbus协议学习总结

    协议简介 Modbus协议,首先从字面理解它包括Mod和Bus两部分,首先它是一种bus,即总线协议,总线就意味着有主机,有从机,这些设备在同一条总线上. Modbus支持单主机,多个从机,最多支持2 ...

  7. RestSharp编写api接口测试,并实现异步调用(不卡顿)

    首先,确保你已经安装了RestSharp NuGet包.如果没有安装,可以通过以下命令安装: bash Install-Package RestSharp 然后,在你的C#代码中,你可以按照以下步骤操 ...

  8. FFmpeg开发笔记(四十一)结合OBS与MediaMTX实现SRT直播推流

    ​<FFmpeg开发实战:从零基础到短视频上线>一书的"10.2  FFmpeg推流和拉流"提到直播行业存在RTSP和RTMP两种常见的流媒体协议.除此以外,还有于20 ...

  9. Activity活动生命相关

    启动与结束 页面跳转: startActivity(new Intent(this,xxxx.class)); 关闭当前界面返回上一界面 finish(); //这里我在使用finish遇到一个问题, ...

  10. ios的idp/iep证书的生成方法,无苹果电脑

    在这个多端开发的年代,出现了很多优秀的开发框架,比如hbuilder和uniapp等等.我们可以使用这些框架来开发APP,假如我们要打包ios的app,则需要一个idp/iep证书. 那么这个证书是如 ...