为什么要探究这个问题?

作为基础设施供应商,自己的服务占用多少内存,为什么要占用这么多内存,需要能说的清楚。作为一个云计算开发,这点问题都弄不清楚,说不过去。

§ 0x01 范围

讨论的只限于Linux X86平台下,因为实用第一。内存页大小为4KiB。

目标:说清楚一个大型的Go进程内存消耗在了哪里。

Go具有自己的运行时,其内存构成比较复杂。想说清楚和容易理解,有些难度。先从较简单的C程序说起。

§ 0x02 先说普通的C程序

正常能写出的最简单的程序,没有任何依赖,当然用汇编写出来的。个人更喜欢AT&T风格的,如下:

.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $5, %ebx
int $0x80

写个Makefile。

all: demo

demo: demo.o
# ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o $@.d -lc $<
ld -static --no-dynamic-linker -o $@ -lc $< demo.o: demo.s
as -gstabs -o $@ $< clean:
rm ./demo ./demo.o

简单说明下过程,通过as编译,然后通过ld进行链接,最终生成一个static link的二进制。它不依赖libc,在Linux下能做到的最小程序。

需要安装gcc和gas等组件。执行make后,生成一个demo的二进制。运行这个二进制,只有一个系统调用就是设置退出码。执行一下,并没有任何输出,因为它只设置了一个退出码。可以通过 echo $?进行确认。想进一步实验,可以改成别的退出码。

$ ./demo; echo $?
5

使用gdb调试下这个程序让它暂停住,看下它的内存占用。

ulin      7116  0.0  0.0    168    24 pts/13   t    09:17   0:00 /mnt/d/code/lean/go-memory/asm/demo

它的RSS值是24,单位是KiB,也就是说它个最小程序的内存占用是24KiB。

下面我们来探究下为什么最小的程序也会占用24KiB内存。进程是由二进制加载到内存中形成的,所以起点还是看二进制,也就是elf的组成。

通过readelf -S看到各段大小如下:

section size(B) 说明
NULL 0 elf中规定的空置段,占了4KiB
.text 代码段 12 大小只有12B,但对齐后16B
.symtab 符号表 144 可以通过strip去除
.strtab 字符串表 25
.shstrtab 33

elf的布局如下:

通过readelf -a -W demo获取。

1. ELF header
2. Program header
3. sections
4. section Header table
区段 偏移 大小
ELF header 0 64
Program header 64 56 * 2 = 112
sections 176 4096 + 16 + 144 + 25 = 4281
section header table 4320 5 * 64 = 320

上各section中,只有.text和会加载到内存中。代码段只有12B,为什么进程的RSS是24KiB。

/proc/<pid>/smaps中有记载详细的内存布局,demo的具体内容摘要如下。先说下Pss与Rss的区别。

Rss是实际段大小,Pss是私有段大小。如果是一个.so文件,有其他进程共享,则Pss=Rss/N。实际占用的是所有段的Pss值之和。

Pss值 说明
00400000-00401000 4KiB 根据 Program Header看,应该是NULL段加载进来的。只读。
00401000-00402000 4KiB 代码段,rxp,可执行。根据 Program Header加载的。
7ffff7ff9000-7ffff7ffd000 4KiB 看注释应该是环境变量。只读。
7ffff7ffd000-7ffff7fff000 4KiB vdso段,内核映射的伪so。
7ffffffdd000-7ffffffff000 8KiB

刚好是24KiB大小。因为内存页的属性是按照最小单页的粒度,所以代码段不足4KiB,但也分配了4KiB。

§ 0x03 小结

我们探讨了最小C程序的内存占用,将其RSS的组成分析清楚了。整体的结论如下:

  1. elf中的部分section会映射到进程的内存中。
  2. 进程中最终的内存section由两种来源:
  • 在Program Header中告诉加载器,把哪些字段加载到内存中,并设置对应的Flag。
  • 加载器自己规定的共用的一些段,如vdso、栈、环境变量段。

疑问:为什么没有看到堆段(即heap)呢?猜测那是glibc规定的,目前我们用的是汇编实现的,不需要用堆。

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. Windows下USB声卡音量调整

    买了一个绿联的USB声卡, 但是默认的音量太大了,最低音量都响的不行. 查了一下, 发现了一个叫EqualizerAPO的软件可以调整输出设备的音量. https://equalizerapo.com ...

  2. ZYNQ:使用 PetaLinux 构建Linux项目

    参考文档:ug1144-petalinux-tools-reference-guide.pdf 环境安装 tofrodos iproute2 gawk gcc g++ git make net-too ...

  3. 下载 Linux 内核的脚本

    介绍 在 类UNIX 环境下运行比较好(基于wget) 包括了 2.6 ~ 4.x 内核的地址. 5.x 因为 还在更新因此不做记录. 脚本下载地址: https://files.cnblogs.co ...

  4. python3 中的装饰器总结

    前言 python3 中有很多好用的装饰器,本编文章作为个人笔记使用,随时更新. 正文 1. @dataclass 内容来源:chatgpt3.5 @dataclass是一个装饰器,用于给类提供自动生 ...

  5. python subprocess读取终端输出内容

    参考链接:https://www.cnblogs.com/songzhenhua/p/9312718.html https://www.cnblogs.com/darkchii/p/9013673.h ...

  6. 组件中 data 为什么是一个函数?

    如果两个实例引用同一个对象,当其中一个实例的属性发生改变时,另一个实例属性也随之改变,对象没有自己的作用域,只有当两个实例拥有自己的作用域时,才不会相互干扰. 这是因为JavaScript的特性所导致 ...

  7. SpringBoot获取指定Resource下的文件内容

    加入依赖 <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</a ...

  8. Using temporary与Using filesort

    Using temporary Using temporary表示由于排序没有走索引.使用union.子查询连接查询.使用某些视图等原因(详见https://dev.mysql.com/doc/ref ...

  9. django 如何查询汇总的求和时避免没有数据导致的错误

    django 如何查询汇总的求和时避免没有数据导致的错误 在 Django 中,如果你希望对某个字段进行求和操作,并在没有数据时返回默认值,可以使用 aggregate 结合 Coalesce 函数. ...

  10. 深入理解 Vue 3 组件通信

    在 Vue 3 中,组件通信是一个关键的概念,它允许我们在组件之间传递数据和事件.本文将介绍几种常见的 Vue 3 组件通信方法,包括 props.emits.provide 和 inject.事件总 ...