golang中make单指创建初始化 chan、slice、map 这3种类型变量,而new是创建对应类型的指针,new入参是Type,其会初始化对应内存为零值。
func main() {
v1 := make([]int64, 0)
v2 := new([]int64)
fmt.Printf("%v, %v\n", reflect.TypeOf(v1), unsafe.Sizeof(v1))
fmt.Printf("%v, %v\n", reflect.TypeOf(v2), unsafe.Sizeof(v2))
}
// 打印结果
[]int64, 24 // 24是因为make(slice,n)由于返回的是结构体
*[]int64, 8
func main() {
slice := make([]int64, 2)
pslice := new([]int64)
slice[0] = 1
slice[1] = 2
pslice = &slice
fmt.Printf("%v %v\n", unsafe.Sizeof(slice), unsafe.Sizeof(pslice))
fmt.Printf("%v %v\n", slice, *pslice)
}
// 打印结果
24 8
[1 2] [1 2]
对于make和new分配的内存,是在堆还是栈上呢? 在继续分析之前,有必要先看下 内存逃逸和逃逸分析:
内存逃逸简单来讲就是 程序中函数都有自己的局部变量和返回地址空间(栈帧),当某个变量想要在函数结束之后继续使用,需要将其分配到堆上,这种从栈上逃逸到堆上的现象 就是内存逃逸。
逃逸分析就是程序在编译期间通过分析函数中变量,哪些需要在堆上分配哪些在栈上分配。逃逸分析为什么是在编译期间做的呢,因为函数局部变量占用多少内存需要在程序编译期间确定,运行期间无法更改。
go的逃逸分析 会遵循2个原则:
- 指向栈对象的指针不能存在堆上。
- 指向栈对象的指针生命周期不能超过该对象生命周期,也就是栈对象指针不能在栈对象销毁后继续存活。
可以使用如下命令查看go编辑期间的逃逸分析:go build -gcflags '-m -m -l'
,比如包含 "xxx escapes to heap"
就说明发生了内存逃逸。逃逸分析减轻了gc压力提高运行效率,对于小对象建议直接使用对象,大对象建议使用指针。
- 对于make和new分配的内存,go编译器尽量将变量分配在栈上,如果变量未发生逃逸,那么就会在栈上分配,否则分配在堆上。
- 如果变量占用内存很大超过了栈空间,或者栈空间不足,那么就会分配在堆上。注意,占用内存很大的局部变量,这个阈值 不同版本golang的大小限制不一样。
make(slice,n)如果切片大小不固定,其会在堆上分配:
func generate(n int) {
nums := make([]int, n) // 不确定大小,堆上分配
for i := 0; i < n; i++ {
nums[i] = rand.Int()
}
}
参考资料: