Go 内存优化与垃圾收集( 三 )


换句话说 , 如果活跃堆大小为10 MB,则当前堆大小达到1 MB时就将触发垃圾收集 。

Go 内存优化与垃圾收集

文章插图
GOGC = 10
在本例中,垃圾收集器被调用了38次,总垃圾收集时间为28 ms 。
Go 内存优化与垃圾收集

文章插图
GOGC=10时的GC调用次数
可以观察到,将GOGC设置为低于100%的值可以增加垃圾收集的频率 , 可能导致CPU使用率增加并降低程序性能 。
更少的调用GC如果运行相同程序 , 但将debug.SetGCPercent(1000)设置为1000%,我们将得到以下结果:
Go 内存优化与垃圾收集

文章插图
GOGC = 1000
可以看到,当前堆的大小一直在增长,直到达到活跃堆大小的1000% 。换句话说,如果活跃堆大小为10 MB,则当前堆大小达到100 MB时将触发垃圾收集 。
Go 内存优化与垃圾收集

文章插图
GOGC=1000时的GC调用次数
在当前情况下,垃圾收集器被调用一次并执行2毫秒 。
关闭GC还可以通过设置GOGC=off或调用debug.SetGCPercent(-1)来禁用垃圾收集 。
下面是禁用垃圾收集器而不设置GOMEMLIMIT时堆的行为:
Go 内存优化与垃圾收集

文章插图
当GC=off时,堆大小不断增长 。
可以看到,在关闭GC后 , 应用程序的堆大小一直在增长 , 直到程序执行为止 。
堆占用多少内存?在活跃堆的实际内存分配中,通常不像我们在trace中看到的那样定期和可预测的工作 。
活跃堆随着每个垃圾收集周期动态变化,并且在某些条件下 , 其绝对值可能出现峰值 。
例如,如果由于多个并行任务的重叠,活跃堆的大小可以增长到800 MB , 那么只有在当前堆大小达到1.6 GB时才会触发垃圾收集 。
Go 内存优化与垃圾收集

文章插图
现代开发通常在具有内存使用限制的容器中运行应用 。因此,如果容器将内存限制设置为1 GB , 并且总堆大小增加到1.6 GB,则容器将失效 , 并出现OOM(out of memory)错误 。
让我们模拟一下这种情况 。例如,我们在内存限制为10 MB的容器中运行程序(仅用于测试目的) 。Dockerfile:
FROM golang:latest as builderWORKDIR /srcCOPY . .RUN go env -w GO111MODULE=onRUN go mod vendorRUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -installsuffix cgo -o App ./cmd/FROM golang:latestWORKDIR /root/COPY --from=builder /src/app .EXPOSE 8080CMD ["./app"]Docker-compose描述:
version: '3'services: my-app:build:context: .dockerfile: Dockerfileports:- 8080:8080deploy:resources:limits:memory: 10M让我们使用前面设置GOGC=1000%的代码启动容器 。
可以使用以下命令运行容器:
docker-compose builddocker-compose up几秒钟后,容器将崩溃,并产生与OOM相对应的错误 。
exited with code 137这种情况非常令人不快: GOGC只控制新堆的相对值,而容器有绝对限制 。
如何避免OOM?从1.19版本开始,在GOMEMLIMIT选项的帮助下,Golang引入了一个名为"软内存管理"的特性,runtime/debug包中名为SetMemoryLimit的类似函数(可以阅读48409-soft-memory-limit.md[6]了解有关此选项的一些有趣的设计细节)提供了相同的功能 。
GOMEMLIMIT环境变量设置Go运行时可以使用的总体内存限制,例如: GOMEMLIMIT = 8MiB 。要设置内存值,需要使用大小后缀,在本例中为8 MB 。
让我们启动将GOMEMLIMIT境变量设置为8MiB的容器 。为此,我们将环境变量添加到docker-compose文件中:
version: '3'services: my-app:environment:GOMEMLIMIT: "8MiB"build:context: .dockerfile: Dockerfileports:- 8080:8080deploy:resources:limits:memory: 10M现在,当启动容器时,程序运行没有任何错误 。该机制是专门为解决OOM问题而设计的 。
这是因为启用GOMEMLIMIT=8MiB后,会定期调用垃圾收集器,并将堆大小保持在一定限制内,结果就是会频繁调用垃圾收集器以避免内存过载 。
Go 内存优化与垃圾收集

文章插图
运行垃圾收集器以使堆大小保持在一定的限制内 。
成本是什么?GOMEMLIMIT是强有力的工具,但也可能适得其反 。
在上面的堆跟踪图中可以看到这种场景的一个示例 。
当总内存大小由于活跃堆或持久程序泄漏的增长而接近GOMEMLIMIT时 , 将开始根据该限制不断调用垃圾收集器 。


推荐阅读