go version go1.13.1 darwin/amd64

尝试做一次实验:

package main

import (
    "fmt"
    "runtime"
)

//var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 241KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 65KB, GC的次数 = 2
设置为nil后:分配的内存 = 65KB, GC的次数 = 3

针对:底层内存不会真正的释放, 这样可能会导致内存一直增长下去造成问题。

可以看到,新版本的 Golang 难道真的会回收 map 的多余空间哦,难道哈希表会随着 map 里面的元素变少,然后缩小了?

我又尝试了一下,将 map 放在外层:

package main

import (
    "fmt"
    "runtime"
)

var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    //a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 243KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 244KB, GC的次数 = 2
设置为nil后:分配的内存 = 67KB, GC的次数 = 3

这时 map 好像内存没变化,直到设置为 nil。

为什么全局变量就会不变呢?

而为什么,局部变量还在使用着,它里面还剩一个元素,为什么就会缩小呢,大家都是 map,空间会一直增长,局部变量有优先权变小?难道 Golang 底层做了一些特殊处理?

于是我又做了一次操作,将局部变量添加一万个数,然后再删除9999个数,再添加9999个,看其变化:

package main

import (
    "fmt"
    "runtime"
)

//var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    for i := 0; i < 10000-1; i++ {
        a[i] = v
    }

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("再一次添加1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 242KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 243KB, GC的次数 = 2
再一次添加1万个键值对后:分配的内存 = 65KB, GC的次数 = 3
设置为nil后:分配的内存 = 65KB, GC的次数 = 4

这次局部变量删除后,和全局变量map一样了,内存耶没变化。

但是添加10000个数后内存反而变小了。

这神奇的 Golang 啊。

map删除元素后map内存是不会释放的,无论是局部还是全局,但引出了上面一个奇怪的问题。

map aa = nil