My program gets the memory from OS but doesn't return it back. It reads files in memory, processes them, and waits for the next ones. Generally, I have small files, but sometimes I have big ones. While my program processes big file it requests a big amount of memory from OS but doesn't return it back.
I have found questions/answers related to using debug.FreeOSMemory()
, but it doesn't work on my code sample.
I have a problem in the real system, but I can reproduce it in a small example:
package main
import (
"fmt"
"math/rand"
"runtime"
"runtime/debug"
)
type Data struct {
a int
b int
c string
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func stat(description string) {
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
fmt.Printf("%s -> Alloc: %d; Sys: %d\n", description, rtm.Alloc, rtm.Sys)
}
func mapAllocate() map[string]Data {
var data = make(map[string]Data)
for i := 0; i < 10000; i++ {
key := randSeq(100)
el := Data{
a: rand.Int(),
b: rand.Int(),
c: randSeq(rand.Intn(10000)),
}
data[key] = el
}
return data
}
func main() {
stat("Start program")
var result map[string]Data
for i := 0; i < 10; i++ {
result = mapAllocate()
stat("Map allocate")
result = make(map[string]Data)
runtime.GC()
debug.FreeOSMemory()
stat("GC call ")
}
fmt.Println(len(result))
runtime.GC()
debug.FreeOSMemory()
for true {
stat("Waiting ")
time.Sleep(30 * time.Second)
}
}
Here is the output:
Start program -> Alloc: 129688; Sys: 71387144
Map allocate -> Alloc: 67501528; Sys: 143804680
GC call -> Alloc: 130264; Sys: 143804680
Map allocate -> Alloc: 67611608; Sys: 143804680
GC call -> Alloc: 130272; Sys: 143804680
Map allocate -> Alloc: 74416536; Sys: 143804680
GC call -> Alloc: 130368; Sys: 143804680
Map allocate -> Alloc: 73419616; Sys: 143804680
GC call -> Alloc: 130568; Sys: 143804680
Map allocate -> Alloc: 74005552; Sys: 143804680
GC call -> Alloc: 130664; Sys: 143804680
Map allocate -> Alloc: 73491408; Sys: 143804680
GC call -> Alloc: 130856; Sys: 143804680
Map allocate -> Alloc: 70013488; Sys: 143804680
GC call -> Alloc: 130856; Sys: 143804680
Map allocate -> Alloc: 73025056; Sys: 143804680
GC call -> Alloc: 130952; Sys: 143804680
Map allocate -> Alloc: 66745168; Sys: 143804680
GC call -> Alloc: 131048; Sys: 143804680
Map allocate -> Alloc: 75094304; Sys: 143804680
GC call -> Alloc: 131336; Sys: 143804680
Of course, I don't call GC in my real application. I use it here to demonstrate my problem.
If understand correctly:
- The program allocates memory from the heap. The first time Go runtime doesn't have enough memory and request it from OS.
- I call GC and it deallocated objects from the memory. But Go runtime doesn't return this memory to OS.
It is a problem for me because the program gets the big file, gets a lot of memory, and never (several days) returns it to OS until the OOM killer kills one of the instances of the program.
Why Go runtime doesn't return this memory to OS and how can I fix it?
OS: Linux and Mac OS