-1

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?

Go playground

OS: Linux and Mac OS

ceth
  • 44,198
  • 62
  • 180
  • 289
  • AFAIK the GC releases memory by it's own decision. Surely after exiting the application. – Markus Zeller May 23 '20 at 09:49
  • 1
    Related / possible duplicate: [Cannot free memory once occupied by bytes.Buffer](https://stackoverflow.com/questions/37382600/cannot-free-memory-once-occupied-by-bytes-buffer/37383604#37383604) – icza May 23 '20 at 10:08
  • @icza, in this question there is a recommendation to use `debug.FreeOSMemory()`, but it doesn't help me. – ceth May 23 '20 at 10:10
  • Garbage collection does not release memory, that’s not its job. The job of the garbage collector is to efficiently reuse allocated memory. If the OOM killer is killing your program, it is simply because the program is allocating too much. Freeing unused memory wont help, because the problem is not your unused memory, it’s that you are using too much at some point. – JimB May 23 '20 at 12:07

2 Answers2

2

Go returns memory, but after some amount of time. Unused memory is almost never the problem.

lenin
  • 46
  • 3
1

Why Go runtime doesn't return this memory to OS

It returns this memory, but not immediately because allocating/returning memory is a costly operation. If you cannot wait for it...

how can I fix it?

You can try runtime/debug.FreeOSMemory if this is really the problem.

Volker
  • 40,468
  • 7
  • 81
  • 87
  • Thanks. I have updated my question and add `FreeOSMemory()` call, but nothing changed in the output. – ceth May 23 '20 at 09:58
  • @demas, because unused memory is almost never the problem. You need to figure out why your using too much, and stop doing that. – JimB May 23 '20 at 12:09
  • @JimB, unused memory is not a problem if you have only one program on the server. But if I have ten programs working in the server at the same time, and each of them gets memory and doesn't return it back - it is going to be a problem. – ceth May 23 '20 at 12:52
  • 1
    @demas, I'm saying unused memory is not the problem, because it will be offered back to the OS if it really is unused. You can watch the gctrace output to see what the garbage collector and scavenger are both doing over time, but t's unlikely the go runtime isn't working as designed. If you can't handle peak memory usage of all instances, then you have a problem when their peaks coincide, and attempting to force the memory usage down is only delaying the inevitable. – JimB May 23 '20 at 13:00
  • @JimB, But if we are speaking about the code I placed in my questions (https://play.golang.org/p/fqisWg9EyX5) - is there any problem with it? Is this memory really unused, right? But I have been running this code multiple hours and the memory was not be returned to the system. – ceth May 23 '20 at 13:07
  • 1
    @demas: your example looks fine. If you're looking at `Sys`, that is virtual address space, and isn't relevant to the actual memory used. – JimB May 23 '20 at 13:15