草庐IT

memory - Go 1.3 垃圾收集器没有将服务器内存释放回系统

coder 2023-04-30 原文

我们编写了最简单的 TCP 服务器(带有少量日志记录)来检查内存占用(参见下面的 tcp-server.go)

服务器只接受连接并且什么都不做。它正在使用 Go 版本 go1.3 linux/amd64 的 Ubuntu 12.04.4 LTS 服务器(内核 3.2.0-61-generic)上运行。

附加的基准测试程序 (pulse.go) 在本例中创建 10k 连接,30 秒后断开连接,重复此循环 3 次,然后连续重复 1k 连接/断开的小脉冲。用于测试的命令是 ./pulse -big=10000 -bs=30。

附上第一张图是记录runtime.ReadMemStats当客户端数量变化500倍时得到的,第二张图是“top”看到的服务器进程的RES内存大小。

服务器以可忽略不计的 1.6KB 内存启动。然后内存由 10k 连接的“大”脉冲设置为 ~60MB(如顶部所示),或大约 16MB“SystemMemory”,如 ReadMemStats 所示。正如预期的那样,当 10K 脉冲结束时,正在使用的内存下降,最终程序开始将内存释放回操作系统,灰色的“已释放内存”线就是证明。

问题在于系统内存(以及相应地,“top”看到的 RES 内存)从未显着下降(尽管它下降了一点,如第二张图所示)。

我们预计,在 10K 脉冲结束后,内存将继续释放,直到 RES 大小达到处理每个 1k 脉冲所需的最小值(如“top”所示为 8m RES,而 2MB in-use 报告为运行时.ReadMemStats)。相反,RES 保持在 56MB 左右,并且在使用中从未从最高值 60MB 下降。

我们希望确保偶尔出现峰值的不规则流量的可扩展性,并能够在同一机器上运行多个在不同时间出现峰值的服务器。有没有办法有效地确保在合理的时间范围内将尽可能多的内存释放回系统?

代码 https://gist.github.com/eugene-bulkin/e8d690b4db144f468bc5 :

server.go:

package main

import (
  "net"
  "log"
  "runtime"
  "sync"
)
var m sync.Mutex
var num_clients = 0
var cycle = 0

func printMem() {
  var ms runtime.MemStats
  runtime.ReadMemStats(&ms)
  log.Printf("Cycle #%3d: %5d clients | System: %8d Inuse: %8d Released: %8d Objects: %6d\n", cycle, num_clients, ms.HeapSys, ms.HeapInuse, ms.HeapReleased, ms.HeapObjects)
}

func handleConnection(conn net.Conn) {
  //log.Println("Accepted connection:", conn.RemoteAddr())
  m.Lock()
  num_clients++
  if num_clients % 500 == 0 {
    printMem()
  }
  m.Unlock()
  buffer := make([]byte, 256)
  for {
    _, err := conn.Read(buffer)
    if err != nil {
      //log.Println("Lost connection:", conn.RemoteAddr())
      err := conn.Close()
      if err != nil {
        log.Println("Connection close error:", err)
      }
      m.Lock()
      num_clients--
      if num_clients % 500 == 0 {
        printMem()
      }
      if num_clients == 0 {
        cycle++
      }
      m.Unlock()
      break
    }
  }
}

func main() {
  printMem()
  cycle++
  listener, err := net.Listen("tcp", ":3033")
  if err != nil {
    log.Fatal("Could not listen.")
  }
  for {
    conn, err := listener.Accept()
    if err != nil {
      log.Println("Could not listen to client:", err)
      continue
    }
    go handleConnection(conn)
  }
}

pulse.go:

package main

import (
  "flag"
  "net"
  "sync"
  "log"
  "time"
)

var (
  numBig = flag.Int("big", 4000, "Number of connections in big pulse")
  bigIters = flag.Int("i", 3, "Number of iterations of big pulse")
  bigSep = flag.Int("bs", 5, "Number of seconds between big pulses")
  numSmall = flag.Int("small", 1000, "Number of connections in small pulse")
  smallSep = flag.Int("ss", 20, "Number of seconds between small pulses")
  linger = flag.Int("l", 4, "How long connections should linger before being disconnected")
)

var m sync.Mutex

var active_conns = 0
var connections = make(map[net.Conn] bool)

func pulse(n int, linger int) {
  var wg sync.WaitGroup

  log.Printf("Connecting %d client(s)...\n", n)
  for i := 0; i < n; i++ {
    wg.Add(1)
    go func() {
      m.Lock()
      defer m.Unlock()
      defer wg.Done()
      active_conns++
      conn, err := net.Dial("tcp", ":3033")
      if err != nil {
        log.Panicln("Unable to connect: ", err)
        return
      }
      connections[conn] = true
    }()
  }
  wg.Wait()
  if len(connections) != n {
    log.Fatalf("Unable to connect all %d client(s).\n", n)
  }
  log.Printf("Connected %d client(s).\n", n)
  time.Sleep(time.Duration(linger) * time.Second)
  for conn := range connections {
    active_conns--
    err := conn.Close()
    if err != nil {
      log.Panicln("Unable to close connection:", err)
      conn = nil
      continue
    }
    delete(connections, conn)
    conn = nil
  }
  if len(connections) > 0 {
    log.Fatalf("Unable to disconnect all %d client(s) [%d remain].\n", n, len(connections))
  }
  log.Printf("Disconnected %d client(s).\n", n)
}

func main() {
  flag.Parse()
  for i := 0; i < *bigIters; i++ {
    pulse(*numBig, *linger)
    time.Sleep(time.Duration(*bigSep) * time.Second)
  }
  for {
    pulse(*numSmall, *linger)
    time.Sleep(time.Duration(*smallSep) * time.Second)
  }
}

最佳答案

首先,请注意 Go 本身并不总是缩小自己的内存空间:

https://groups.google.com/forum/#!topic/Golang-Nuts/vfmd6zaRQVs

The heap is freed, you can check this using runtime.ReadMemStats(), but the processes virtual address space does not shrink -- ie, your program will not return memory to the operating system. On Unix based platforms we use a system call to tell the operating system that it can reclaim unused parts of the heap, this facility is not available on Windows platforms.

但你不是在 Windows 上,对吧?

嗯,这个帖子不太确定,但它说:

https://groups.google.com/forum/#!topic/golang-nuts/MC2hWpuT7Xc

As I understand, memory is returned to the OS about 5 minutes after is has been marked as free by the GC. And the GC runs every two minutes top, if not triggered by an increase in memory use. So worst-case would be 7 minutes to be freed.

In this case, I think that the slice is not marked as freed, but in use, so it would never be returned to the OS.

您可能没有等待足够长的时间来等待 GC 扫描,然后是操作系统返回扫描,这可能在最后一个“大”脉冲之后长达 7 分钟。您可以使用 runtime.FreeOSMemory 显式强制执行此操作,但请记住,除非已运行 GC,否则它不会执行任何操作。

(编辑:请注意,您可以使用 runtime.GC() 强制进行垃圾收集,但显然您需要小心使用它的频率;您可以通过突然向下的尖峰来同步它在连接中)。

顺便说一句,我找不到明确的来源(除了我发布的第二个帖子有人提到了同样的事情),但我记得它被多次提到并不是 Go 使用的所有内存是“真实”的内存。如果它是由运行时分配但实际上并没有被程序使用,那么无论 topMemStats 说什么,操作系统实际上都在使用内存,所以内存量该程序是“真的”使用经常被高估。


编辑:正如评论中的 Kostix 注释并支持 JimB 的回答,这个问题被交叉发布在 Golang-nuts 上,我们从 Dmitri Vyukov 那里得到了一个相当明确的回答:

https://groups.google.com/forum/#!topic/golang-nuts/0WSOKnHGBZE/discussion

I don't there is a solution today. Most of the memory seems to be occupied by goroutine stacks, and we don't release that memory to OS. It will be somewhat better in the next release.

所以我所概述的仅适用于堆变量,Goroutine 堆栈上的内存永远不会被释放。这与我最后一个“并非所有显示的分配的系统内存都是‘真实内存’”这一点的交互作用还有待观察。

关于memory - Go 1.3 垃圾收集器没有将服务器内存释放回系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24376817/

有关memory - Go 1.3 垃圾收集器没有将服务器内存释放回系统的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  6. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  7. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

  8. 键删除后 ruby​​ 哈希内存泄漏 - 2

    你好,我无法成功如何在散列中删除key后释放内存。当我从哈希中删除键时,内存不会释放,也不会在手动调用GC.start后释放。当从Hash中删除键并且这些对象在某处泄漏时,这是预期的行为还是GC不释放内存?如何在Ruby中删除Hash中的键并在内存中取消分配它?例子:irb(main):001:0>`ps-orss=-p#{Process.pid}`.to_i=>4748irb(main):002:0>a={}=>{}irb(main):003:0>1000000.times{|i|a[i]="test#{i}"}=>1000000irb(main):004:0>`ps-orss=-p

  9. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

  10. ruby-on-rails - HTTParty 的内存问题和下载大文件 - 2

    这会导致Ruby出现内存问题吗?我知道如果大小超过10KB,Open-URI会写入TempFile。但是HTTParty会在写入TempFile之前尝试将整个PDF保存到内存吗?src=Tempfile.new("file.pdf")src.binmodesrc.writeHTTParty.get("large_file.pdf").parsed_response 最佳答案 您可以使用Net::HTTP。参见thedocumentation(特别是标题为“流媒体响应机构”的部分)。这是文档中的示例:uri=URI('http://e

随机推荐