承上启下
在上一篇文章中,我们介绍了go的指针类型,*和Unsafe.Point以及uintptr的区别。在高级编程语言中,例如Java,是直接忽略掉指针这个概念的。像C++,是能够自由使用指针的,但是C++需要自己进行内存的分配和回收。为什么Go要构建这三种指针类型呢,也是为了方便内存。如果对Go语言了解没那么深入或者系统要求没那么多的,一般也不会用到指针类型,这就保证了整个Go应用的高效性。既然已经说到了内存分配,咱们今天就来说说Go的内存分配把。
开始学习
Go的内存分配
Go语言的内存分配机制是Go运行时(runtime)的重要组成部分,它负责在程序运行时动态地管理内存。Go的内存分配器采用了多种策略来优化性能和减少内存碎片,下面是一些关键的点:
内存分配器组件
-
堆(Heap):Go的垃圾回收(GC)主要在堆上操作,动态分配的内存大多来自于堆。
-
栈(Stack):函数调用时使用的内存,通常是自动分配和释放的。Go语言使用连续栈技术,可以在需要时扩展。
-
内存管理器(Memory Manager):负责协调内存的分配和释放。
内存分配策略
-
对象大小:
- 小对象(Tiny):小于16字节的对象,Go会尝试在分配的内存块中填充多个小对象以减少内存碎片。
- 小到大对象(Small):16字节到32KB的对象,通过尺寸分类进行快速分配。
- 大对象(Large):大于32KB的对象,直接在堆上分配。
-
尺寸分类:
- Go的内存分配器将内存分为多个大小等级,每个等级的对象大小是上一个等级的两倍。
-
缓存(Cache):
- 每个处理器P都有一个mcache(内存缓存),用于无锁访问,快速分配小对象。
- 对于中心缓存(mcentral),它存储着所有大小等级的空闲内存块列表。
-
线程局部存储(Thread-Local Storage, TLS):
- 为了减少锁的竞争,每个线程都有一个本地的缓存(mcache)。
内存分配过程
-
分配:
- 当一个goroutine需要内存时,它会检查mcache中对应大小等级的空闲列表。
- 如果mcache没有足够的内存,它会从mcentral获取一个新的内存块。
- 如果mcentral也没有足够的内存,它会从堆(mheap)中获取一个新的更大的内存块,并将其切分成适当大小的块。
-
释放:
- 当对象不再被使用时,它们会被标记为垃圾,等待垃圾回收器来回收。
- 回收过程中,内存会被返回到相应的mcache或mcentral,供后续分配使用。
垃圾回收(GC)
Go使用标记-清除(Mark-Sweep)和标记-整理(Mark-Compact)算法来回收不再使用的内存。垃圾回收是自动的,对开发者透明。这节主要讲的是内存分配,所以对于内存回收的三色标记法,读写屏障之类的就不过多赘述了。
Make和New
我们对变量初始化的时候,其实就是给这个变量分配一定的内存空间。这时候就不得不说道new和make的区别了。这两个都是内置函数,用于初始化变量,无论是 new
还是 make
,它们分配的内存都是在堆上,这意味着分配的内存直到不再被引用时才会被垃圾回收器回收。但是他们具体是怎么样的呢,我我们一起来看看把。
new
关键字
-
用途 :
new
用于值类型的内存分配,如基本数据类型(int、float、string等)、数组、结构体等。 -
返回值 :
new
返回的是指向该类型零值的指针。 -
分配位置:分配的内存是在堆上。
-
示例 :
p := new(int) // p 是 *int 类型,指向一个新的分配的 int 值,其值为 0
make
关键字
-
用途 :
make
仅用于内建类型(map、slice 和 channel)的内存分配和初始化。 -
返回值 :
make
返回的是初始化后的(非零)值,而不是指针。 -
分配位置:分配的内存也是在堆上。
-
示例 :
s := make([]int, 10) // s 是 []int 类型的切片,长度为 10,每个元素都初始化为 0
区别
-
返回类型:
new(T)
返回的是*T
类型的指针。make(T, args)
返回的是T
类型的值 ,其中T
必须是 slice、map 或 channel。
-
初始化:
new
只分配内存,并不初始化内存,内存的值是零值。make
分配并初始化内存,返回的是已经初始化后的值。
-
使用场景:
- 对于值类型(如数组、结构体等)使用
new
。 - 对于内建类型(如 slice、map、channel)使用
make
。
- 对于值类型(如数组、结构体等)使用
与内存分配的联系
-
堆分配 :无论是
new
还是make
,它们分配的内存都是在堆上,这意味着分配的内存直到不再被引用时才会被垃圾回收器回收。 -
性能考虑 :由于
make
还负责初始化内存,因此对于需要立即使用的内建类型来说,使用make
可以减少后续的初始化步骤,可能带来性能上的优势。 -
内存管理 :
new
和make
都是由Go的内存分配器管理的,它们负责从堆上分配内存,并可能涉及Go运行时的内存管理策略,如mcache、mcentral和mheap等。