为什么我的 Go 服务器内存泄漏?



我写了一个简单的TCP服务器。
问题是,当对它进行压力测试时,内存使用量似乎在急剧增加,并且在测试完成后并没有减少。 当服务器启动时,需要 ~700KB。
在压力测试期间和之后,内存使用量跃升至 ~7MB。
这是我的代码:

package main
import (
"net"
"log"
"fmt"
"bufio"
)
func main() {
ln, err := net.Listen("tcp", ":8888")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handle(conn)
}
}
func handle(conn net.Conn) {
defer conn.Close()
fmt.Println("Accepted", conn.LocalAddr())
for {
buf, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
break
}
msg := string(buf[:len(buf)-2])
fmt.Println("Received", msg)
conn.Write([]byte("OKn"))
}
}

任何帮助都非常感谢。

注意:我正在使用tcpkali来加载它。这是命令行:

tcpkali -em "testingrn" -c 100 -r 1000 -T 60  127.0.0.1:8888

编辑:在下面的一些评论之后,我运行了一些测试,结果如下:

  1. 已启动服务器并运行tcpkali
  2. 第一次运行后,RSS 在 8516。
  3. 第二次运行后,RSS攀升至8572。
  4. 服务器现在处于空闲状态。 5分钟后,RSS攀升至8588。
  5. 又过了5分钟,RSS攀升至8608,看起来很稳定。
  6. 休息15分钟后,我又跑tcpkali,RSS攀升到8684。
  7. 休息几分钟,又跑tcpkali,RSS攀升至8696。
  8. 休息几分钟,又跑tcpkali,RSS攀升至8704。
  9. 休息几分钟,又跑tcpkali,RSS攀升至8712。

现在,我不知道你怎么称呼它,但我称之为内存泄漏。这里有些不对劲。没有释放内存,每次我运行测试时,RSS 都会不断攀升。显然,这个东西不能部署到生产环境,因为它最终会消耗所有可用的内存。
我也试着打电话给os.FreeOSMemory()但没有任何反应。

我的系统是 macOS 1.9.4 上的 Go 10.13.1。这个环境是相关的还是我错过了什么?

最后更新:
在 Ullrich @Steffen答案和在我的环境中失败的测试之后,我在 Ubuntu 服务器上尝试了一下,几分钟的空闲时间后内存被释放。
似乎macOS存在问题。

Go 不会立即释放从操作系统分配的内存。原因可能是分配内存的成本很高(需要系统调用),并且在不久的将来再次需要它的可能性很高。但是,如果内存得到足够长的未使用时间,它最终将被释放,以便进程的RSS再次降低。

再次进行测试并进行轻微修改将显示这一点(至少对我来说是这样):

  1. 开始编程并查看RSS。
  2. 运行tcpkali,等待tcpkali结束,然后再次查看RSS。现在它要高得多,因为程序需要大量内存来完成预期任务。
  3. 不要停止程序,而是再次运行 tcpkali 并再次等待它结束。在查看 RSS 时,您应该看到它没有进一步增长(太多)。这意味着程序再次使用已分配的内存,不需要从系统中分配新内存。
  4. 现在监视 RSS 并等待。过了一会儿(在我的系统上大约 10 分钟),您应该会看到 RSS 再次关闭。这是因为程序现在已经确定分配但未使用的内存可能不再使用,并将内存返回到操作系统。

请注意,并非所有内存都可能归还。根据了解 Go Lang 内存使用情况,它不会返回(在 go 1.3 中)用于 go 例程堆栈的内存,因为将来更有可能需要它。

对于测试,您还可以在关键位置添加一些debug.FreeOSMemory()(从runtime/debug开始)(例如当您在goroutine中脱离循环时),以便内存更早地返回到操作系统。但是,鉴于延迟返回内存是为了性能,这种显式释放可能会影响性能。

最新更新