Memory 转到1.3垃圾收集器不将服务器内存释放回系统

Memory 转到1.3垃圾收集器不将服务器内存释放回系统,memory,memory-management,go,Memory,Memory Management,Go,我们编写了最简单的TCP服务器(带有少量日志记录)来检查内存占用(请参阅下面的TCP-server.go) 服务器只接受连接,什么也不做。它运行在Ubuntu12.04.4LTS服务器(内核3.2.0-61-generic)上,Go版本为go1.3Linux/amd64 在本例中,所附的基准测试程序(pulse.go)创建10k连接,在30秒后断开连接,重复此循环三次,然后连续重复1k连接/断开的小脉冲。用于测试的命令是./pulse-big=10000-bs=30 当客户端数量更改500倍时,

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

服务器只接受连接,什么也不做。它运行在Ubuntu12.04.4LTS服务器(内核3.2.0-61-generic)上,Go版本为go1.3Linux/amd64

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

当客户端数量更改500倍时,通过记录runtime.ReadMemStats获得第一个附加图,第二个图是服务器进程“top”看到的RES内存大小

服务器启动时只有微不足道的1.6KB内存。然后,内存由10k连接的“大”脉冲设置为~60MB(见上图),或由ReadMemStats设置为约16MB的“系统内存”。正如预期的那样,当10K脉冲结束时,正在使用的内存下降,最终程序开始将内存释放回操作系统,灰色的“已释放内存”线证明了这一点

问题是系统内存(以及相应的“top”所示的RES内存)从未显著下降(尽管如第二张图所示下降了一点)

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

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

代码:

server.go:

主程序包
进口(
“净额”
“日志”
“运行时”
“同步”
)
var m sync.Mutex
var num_客户端=0
风险值周期=0
func printMem(){
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
log.Printf(“周期#%3d:%5d客户端|系统:%8d使用:%8d已发布:%8d对象:%6d\n”,周期,num#u客户端,ms.HeapSys,ms.HeapInuse,ms.heapprereleased,ms.HeapObjects)
}
func handleConnection(conn net.conn){
//Println(“接受的连接:”,conn.RemoteAddr())
m、 锁()
num_客户端++
如果num_客户端%500==0{
printMem()
}
m、 解锁()
缓冲区:=生成([]字节,256)
为了{
_,err:=conn.Read(缓冲区)
如果错误!=零{
//log.Println(“连接中断:”,conn.RemoteAddr())
错误:=连接关闭()
如果错误!=零{
Println(“连接关闭错误:”,err)
}
m、 锁()
num_客户端--
如果num_客户端%500==0{
printMem()
}
如果num_clients==0{
循环++
}
m、 解锁()
打破
}
}
}
func main(){
printMem()
循环++
侦听器,err:=net.Listen(“tcp”,“3033”)
如果错误!=零{
log.Fatal(“无法侦听”)
}
为了{
conn,err:=listener.Accept()
如果错误!=零{
Println(“无法侦听客户端:”,错误)
持续
}
go Handle连接(连接)
}
}
pulse.go:

主程序包
进口(
“旗帜”
“净额”
“同步”
“日志”
“时间”
)
变量(
numBig=flag.Int(“大”,4000,“大脉冲中的连接数”)
bigIters=flag.Int(“i”,3,“大脉冲的迭代次数”)
bigSep=flag.Int(“bs”,5,“大脉冲之间的秒数”)
numSmall=flag.Int(“小”,1000,“小脉冲中的连接数”)
smallSep=flag.Int(“ss”,20,“小脉冲之间的秒数”)
linger=flag.Int(“l”,4,“断开连接前连接应保持多长时间”)
)
var m sync.Mutex
var活动连接=0
var connections=make(映射[net.Conn]bool)
func脉冲(n int,linger int){
var wg sync.WaitGroup
log.Printf(“正在连接%d个客户端…”n“,n)
对于i:=0;i0{
log.Fatalf(“无法断开所有%d个客户端[%d个剩余]。\n”,n,len(连接))
}
log.Printf(“已断开%d个客户端的连接)。\n”,n)
}
func main(){
flag.Parse()
对于i:=0;i<*bigIters;i++{
脉搏(*numBig,*linger)
时间睡眠(时间持续时间(*bigSep)*时间秒)
}
为了{
脉冲(*numSmall,*linger)
时间睡眠(时间持续时间(*smallSep)*时间秒)
}
}

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

堆已释放,您可以使用runtime.ReadMemStats()检查这一点, 但是进程的虚拟地址空间并没有缩小--即,您的 程序不会将内存返回到操作系统。基于Unix平台 平台我们使用系统调用告诉操作系统 无法回收堆中未使用的部分,此功能不可用 在Windows平台上

但你不在Windows上,对吗

嗯,这条线索不太明确,但它说:

作为
package main

import (
    "runtime/debug"
    "time"
)

func main() {
    go periodicFree(1 * time.Minute)

    // Your program goes here

}

func periodicFree(d time.Duration) {
    tick := time.Tick(d)
    for _ = range tick {
        debug.FreeOSMemory()
    }
}