三个结论:
golang的垃圾回收是针对堆的 (垃圾回收都是针对堆的,这里只是做一个简单的证明)
引用类型的全局变量内存分配在堆上,值类型的全局变量分配在栈上
堆和栈的简单说明:
1.栈(操作系统):由操作系统自动分配释放
2.堆(操作系统): 一般由程序员分配释放,例如在c/c++中,在golang,java,python有自动的垃圾回收机制
我们都知道变量占有内存,内存在底层分配上有堆和栈。
注意这里说的是"通常"
,因为变量又分为局部变量和全局变量。
以下是摘录go圣经的一部分:
Copy 编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。因此,y并没有从函数g中逃逸,编译器可以选择在栈上分配 y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
下面做实验来证明三个结论:
实验一:
说明的问题:引用类型的全局变量的内存可以被垃圾回收
Copy package main
import (
"log"
"runtime"
)
var lastTotalFreed uint64 // size of last memory has been freed
func InitMap(a map[int]int) {
for i := 0; i < 100000; i++ {
a[i] = i
}
}
func ClearMap(a map[int]int) {
for i := 0; i < 100000; i++ {
delete(a, i)
}
}
var a map[int]int
func main() {
a = make(map[int]int)
printMemStats()
InitMap(a)
runtime.GC()
printMemStats()
ClearMap(a)
runtime.GC()
printMemStats()
a = nil
runtime.GC()
printMemStats()
}
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
lastTotalFreed = m.TotalAlloc - m.Alloc
}
实验结果:
Copy 2019/12/19 17:03:59 Alloc = 88 TotalAlloc = 88 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 17:03:59 Alloc = 2798 TotalAlloc = 5739 Just Freed = 2940 Sys = 9502 NumGC = 2
2019/12/19 17:03:59 Alloc = 2799 TotalAlloc = 5740 Just Freed = 1 Sys = 9502 NumGC = 3
2019/12/19 17:03:59 Alloc = 91 TotalAlloc = 5741 Just Freed = 2708 Sys = 9502 NumGC = 4
实验结论:
引用类型的全局变量的内存被垃圾回收,先不下结论引用类型的全局变量的内存分配在栈上还是堆上
实验二:
说明的问题:引用类型的局部变量的内存没被垃圾回收
将代码中的a改为局部变量,其他地方完全不懂。为了简洁,就不上代码了
直接看实验结果:
Copy 2019/12/19 17:11:13 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 17:11:13 Alloc = 2797 TotalAlloc = 5737 Just Freed = 2940 Sys = 9566 NumGC = 2
2019/12/19 17:11:13 Alloc = 2797 TotalAlloc = 5738 Just Freed = 1 Sys = 9566 NumGC = 3
2019/12/19 17:11:13 Alloc = 2797 TotalAlloc = 5740 Just Freed = 1 Sys = 9566 NumGC = 4
实验结论:
引用类型的局局变量的内存没被垃圾回收,先不下结论引用类型的局局变量的内存分配在栈上还是堆上
中间说明
做了以上两个实验。只能知道内存是否被垃圾回收,至于回收的是栈上内存还是堆上内存不得而知。原因在于不知道变量内存在栈上还是堆上。
为了解决这一问题,我们按照go圣经所说,将局部变量做一步文中的处理,是它必须在推上分配内存
实验三:
说明的问题:
实验代码:
Copy package main
import (
"log"
"runtime"
)
var lastTotalFreed uint64 // size of last memory has been freed
func InitMap(a map[int]int) {
for i := 0; i < 100000; i++ {
a[i] = i
}
}
func ClearMap(a map[int]int) {
for i := 0; i < 100000; i++ {
delete(a, i)
}
}
var global *map[int]int
func main() {
a := make(map[int]int)
printMemStats()
global = &a
InitMap(a)
runtime.GC()
printMemStats()
ClearMap(a)
runtime.GC()
printMemStats()
a = nil
runtime.GC()
printMemStats()
}
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
lastTotalFreed = m.TotalAlloc - m.Alloc
}
实验结果:
Copy 2019/12/19 17:22:42 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 17:22:42 Alloc = 2794 TotalAlloc = 5731 Just Freed = 2937 Sys = 9566 NumGC = 2
2019/12/19 17:22:42 Alloc = 2794 TotalAlloc = 5732 Just Freed = 1 Sys = 9566 NumGC = 3
2019/12/19 17:22:42 Alloc = 91 TotalAlloc = 5733 Just Freed = 2704 Sys = 9566 NumGC = 4
实验结论:
由于对局部变量做了处理,使其确定在堆上分配内存,就被垃圾回收了
由此证明,Go语言的GC回收只回收堆上的内存