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

时间:2018-04-15 13:36:41

标签: 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("OK\n"))
    }
}

非常感谢任何帮助。

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

tcpkali -em "testing\r\n" -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。
  10. 现在,我不知道你称之为什么,但我称之为内存泄漏。这里不对劲。没有内存被释放,每次运行测试时RSS都会不断攀升。显然,这个东西不能部署到生产中,因为它最终将消耗所有可用的内存 我也尝试过调用os.FreeOSMemory()但没有任何反应。

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

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

1 个答案:

答案 0 :(得分:3)

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

稍微修改后再次进行测试会显示(至少对我来说):

  1. 启动程序并查看RSS。
  2. 运行tcpkali,等待tcpkali结束并再次查看RSS。它现在要高得多,因为程序需要大量的内存来完成预定的任务。
  3. 不要停止程序,但再次运行tcpkali并再次等待它结束。在查看RSS时,您应该看到它没有进一步增长。这意味着程序再次使用已分配的内存,不需要从系统分配新内存。
  4. 现在监控RSS并等待。过了一会儿(在我的系统上大约10分钟)你应该看到RSS再次下降。这是因为程序现在已经确定已分配但未使用的内存可能不再使用,并将内存返回给操作系统。
  5. 请注意,并非所有内存都可以返回。根据{{​​3}},它不会返回(在1.3中)用于go例程堆栈的内存,因为将来更有可能需要这样做。

    为了进行测试,您还可以在战略位置添加一些debug.FreeOSMemory()(来自runtime/debug)(例如当您在goroutine中退出循环时),以便先将内存返回到操作系统。但鉴于内存的懒惰回归是为了提高性能,这种显式释放可能会影响性能。