这里不讲垃圾回收的机制
先给出三个结论:
- golang的垃圾回收是针对堆的(垃圾回收都是针对堆的,这里只是做一个简单的证明)
- 引用类型的全局变量内存分配在堆上,值类型的全局变量分配在栈上
- 局部变量内存分配可能在栈上也可能在堆上
堆和栈的简单说明:
1.栈(操作系统):由操作系统自动分配释放
2.堆(操作系统): 一般由程序员分配释放,例如在c/c++中,在golang,java,python有自动的垃圾回收机制
我们都知道变量占有内存,内存在底层分配上有堆和栈。
- 值类型变量的内存通常是在栈中分配
- 引用类型变量的内存通常在堆中分配
注意这里说的是"通常"
,因为变量又分为局部变量和全局变量。
- 当变量是全局变量时,符合上面所说的分配规则
- 但当变量是局部变量时,分配在堆和栈就变的不确定了
以下是摘录go圣经的一部分:
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用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方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
个人理解补充说明:go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。
可参考 Golang中的逃逸分析
下面做实验来证明三个结论:
(以下实验方法说明请参照Go的原生map中删除元素,内存会自动释放吗?)
实验一:
说明的问题:引用类型的全局变量的内存可以被垃圾回收
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
}