Google App Engine Go 内存管理

Att*_* O. 4 memory google-app-engine memory-management go

我最近遇到了一个问题,App Engine 会终止我的 Go 实例,因为它说内存不足。实例的内存限制设置为 128Mb。

但是,我很难弄清楚所有内存都分配在哪里。当我运行以下代码时:

var s runtime.MemStats
runtime.ReadMemStats(&s)
c.Debugf("allocated memory: %d", s.Alloc)
Run Code Online (Sandbox Code Playgroud)

它显示当分配的内存达到大约 39-40Mb 时,我的应用程序会终止并出现以下错误:

总共处理 1 个请求后,超出了软私有内存限制,达到 135.082 MB

同样,当runtime.ReadMemStats(&s)指示我正在使用 20 Mb 时,App Engine 控制台显示我的实例正在使用 92 Mb。重做相同的请求,runtime.ReadMemStats(&s)仍然显示 20Mb,而 App Engine 控制台显示 119Mb。

我已经禁用了appstats,仍然没有帮助。

我的大部分内存都被内存缓存耗尽,我可以减少内存缓存以适应约束(或增加实例的内存限制),但是我想知道所有内存都被用在哪里。如果有人可以阐明这一点,或者如何正确分析 App Engine 上的内存使用情况,那将会有很大帮助。

更新:设法在本地重现此内容。

以下是一个示例应用程序,它在一个请求中分配一些整数,并在下一个请求中对它们进行垃圾收集:

// Package test implements a simple memory test for Google App Engine.
package test

import (
    "net/http"
    "runtime"

    "appengine"
)

var buffer []int64

func init() {
    http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
    var s runtime.MemStats
    c := appengine.NewContext(r)
    if len(buffer) == 0 {
        // Allocate 2^22 integers.
        runtime.ReadMemStats(&s)
        c.Debugf("Memory usage: %d bytes (%d system).", s.Alloc, s.Sys)
        buffer = make([]int64, 4*1024*1024)
        for i, _ := range buffer {
            buffer[i] = int64(i*i)
        }
        runtime.ReadMemStats(&s)
        c.Debugf("Memory usage increased to: %d bytes (%d system).", s.Alloc, s.Sys)
    } else {
        // Remove all references to the slice pointed to by buffer.
        // This should mark it for garbage collection.
        runtime.ReadMemStats(&s)
        c.Debugf("Memory usage: %d bytes (%d system).", s.Alloc, s.Sys)
        buffer = nil
        runtime.GC()
        runtime.ReadMemStats(&s)
        c.Debugf("After GC event: %d bytes (%d system).", s.Alloc, s.Sys)
    }
    w.WriteHeader(http.StatusTeapot)
}
Run Code Online (Sandbox Code Playgroud)

使用开发服务器运行时:

$ ./go_appengine/dev_appserver.py test

2013/09/16 12:28:28 DEBUG: Memory usage: 833096 bytes (272681032 system).
2013/09/16 12:28:28 DEBUG: Memory usage increased to: 34335216 bytes (308332616 system).
INFO     2013-09-16 12:28:28,884 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:29 DEBUG: Memory usage: 34345896 bytes (308332616 system).
2013/09/16 12:28:29 DEBUG: After GC event: 781504 bytes (308332616 system).
INFO     2013-09-16 12:28:29,560 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:30 DEBUG: Memory usage: 791616 bytes (308332616 system).
2013/09/16 12:28:30 DEBUG: Memory usage increased to: 34337392 bytes (308332616 system).
INFO     2013-09-16 12:28:30,276 module.py:593] default: "GET / HTTP/1.1" 418 -
2013/09/16 12:28:36 DEBUG: Memory usage: 34347536 bytes (308332616 system).
2013/09/16 12:28:36 DEBUG: After GC event: 783632 bytes (308332616 system).
INFO     2013-09-16 12:28:36,224 module.py:593] default: "GET / HTTP/1.1" 418 -
Run Code Online (Sandbox Code Playgroud)

看来内存分配和垃圾收集工作正常。然而,从ps输出来看,释放内存似乎并没有减少进程的虚拟内存使用量:

$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 381248 56608
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 676324 57652
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856
$ ps axo command,vsize,rss | ag go_app
/usr/bin/python2.7 ./go_app 750056 57856
Run Code Online (Sandbox Code Playgroud)

运行底层 Go 实例的 Python 进程似乎不断增加其虚拟内存,但它永远不会被释放。生产服务器上似乎也发生了类似的事情:实例运行时报告的已分配内存与内核报告的已用内存不同。

正如@Kluyg 所建议的,管理控制台似乎显示了系统分配的内存,这是有道理的。

Klu*_*uyg 5

根据文档 Alloc字段显示已分配且仍在使用的字节。然而,在垃圾收集语言中,当 GC 释放内存时,它不会立即返回到系统(很快就可以再次请求它,所以为什么要费力地将其返回呢?)。所以你真正需要监控的是Sys字段,它计算从系统获取的字节数。您可能对本文感兴趣,了解如何最大限度地减少内存使用的一些见解。