Golang链接共享库导致内存占用增加的问题
Golang链接共享库导致内存占用增加的问题 系统信息 Linux host1 4.4.0-131-generic #157-Ubuntu SMP 2018年7月12日 星期四 15:51:36 UTC x86_64 x86_64 x86_64 GNU/Linux Go版本 go version go1.10.4 linux/amd64
我有一个如下的Hello World应用程序:
package main
import "fmt"
func main() {
fmt.Println("hello world")
for {
}
}
当我使用-linkshared标志编译时,看到RES内存变得非常高,约20MB 然而,当我静态构建不使用linkshared标志时,RES内存占用只有约1MB
这是怎么回事?我没想到使用共享库(linkshared)时RES内存会增加。应该只有共享内存会增加才对…
我启动了3个应用程序实例。
静态链接构建(1.4MB内存占用)
go build hello-world.go
$ top -b -n1 | grep hello
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
59838 root 20 0 3164 1428 1020 R 106.7 0.0 0:12.26 hello-world
59847 root 20 0 3164 1428 1020 R 106.7 0.0 0:10.05 hello-world
59853 root 20 0 3164 1480 1084 R 100.0 0.0 0:08.63 hello-world
$ ldd hello-world
not a dynamic executable
共享库构建(20MB内存占用)
go build -linkshared hello-world.go
$ top -b -n1 | grep hello
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
61395 root 20 0 158820 20396 12684 R 106.2 0.2 0:12.85 hello-world
61405 root 20 0 158820 20484 12760 R 106.2 0.2 0:11.38 hello-world
61385 root 20 0 158820 20464 12744 R 100.0 0.2 0:15.11 hello-world
$ ldd hello-world
linux-vdso.so.1 => (0x00007ffff6ed1000)
libstd.so => /usr/lib/go-1.10/pkg/linux_amd64_dynlink/libstd.so (0x00007f40e9d8a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f40e99c0000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f40e97bc000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f40e959f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f40ec218000)
更多关于Golang链接共享库导致内存占用增加的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
据我所知,该库的常驻部分(除了共享部分之外)也会被计入RSS。我敢肯定这些库还会分配各种资源,比如用于各类功能的表结构。
更多关于Golang链接共享库导致内存占用增加的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用-linkshared标志编译Go程序时内存占用增加是正常现象,这主要源于动态链接机制的内存管理方式。以下是具体原因分析:
内存占用差异的原因
-
共享库的加载机制:当使用动态链接时,整个Go运行时库(libstd.so)会被映射到每个进程的地址空间中,虽然这部分内存可以在进程间共享,但RES(常驻内存)统计仍会计算每个进程映射的完整大小。
-
内存页对齐开销:动态库通常按内存页边界对齐加载,即使只使用库中的少量功能,也会加载整个库的文本段和数据段。
-
全局数据段复制:共享库中的可写数据段(BSS、DATA)会在每个进程中有独立的副本,无法在进程间共享。
示例验证
可以通过以下代码验证内存分配情况:
package main
import (
"fmt"
"runtime"
"time"
)
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
func main() {
printMemStats()
// 分配一些内存来观察变化
data := make([]byte, 1024*1024) // 1MB
for i := range data {
data[i] = 1
}
printMemStats()
// 保持程序运行以便观察top输出
for {
time.Sleep(time.Second)
}
}
技术细节
使用-linkshared时,Go编译器会:
- 将Go运行时、标准库等编译为共享库
libstd.so - 生成依赖该共享库的可执行文件
- 每个进程加载完整的
libstd.so到内存中
静态链接则将所有需要的代码编译到单个可执行文件中,内存管理更加紧凑。
性能对比
可以通过以下命令查看具体的内存映射:
# 查看进程内存映射
cat /proc/<pid>/maps
# 查看更详细的内存信息
cat /proc/<pid>/smaps
这种内存占用差异是动态链接架构的固有特性,在需要运行多个Go进程实例且内存受限的环境中,静态链接通常是更好的选择。

