【Go系列】Go的内存分配

avatar
作者
猴君
阅读量:2

承上启下

        在上一篇文章中,我们介绍了go的指针类型,*和Unsafe.Point以及uintptr的区别。在高级编程语言中,例如Java,是直接忽略掉指针这个概念的。像C++,是能够自由使用指针的,但是C++需要自己进行内存的分配和回收。为什么Go要构建这三种指针类型呢,也是为了方便内存。如果对Go语言了解没那么深入或者系统要求没那么多的,一般也不会用到指针类型,这就保证了整个Go应用的高效性。既然已经说到了内存分配,咱们今天就来说说Go的内存分配把。

开始学习

Go的内存分配

        Go语言的内存分配机制是Go运行时(runtime)的重要组成部分,它负责在程序运行时动态地管理内存。Go的内存分配器采用了多种策略来优化性能和减少内存碎片,下面是一些关键的点:

内存分配器组件

  1. 堆(Heap):Go的垃圾回收(GC)主要在堆上操作,动态分配的内存大多来自于堆。

  2. 栈(Stack):函数调用时使用的内存,通常是自动分配和释放的。Go语言使用连续栈技术,可以在需要时扩展。

  3. 内存管理器(Memory Manager):负责协调内存的分配和释放。

内存分配策略

  1. 对象大小

    • 小对象(Tiny):小于16字节的对象,Go会尝试在分配的内存块中填充多个小对象以减少内存碎片。
    • 小到大对象(Small):16字节到32KB的对象,通过尺寸分类进行快速分配。
    • 大对象(Large):大于32KB的对象,直接在堆上分配。
  2. 尺寸分类

    • Go的内存分配器将内存分为多个大小等级,每个等级的对象大小是上一个等级的两倍。
  3. 缓存(Cache)

    • 每个处理器P都有一个mcache(内存缓存),用于无锁访问,快速分配小对象。
    • 对于中心缓存(mcentral),它存储着所有大小等级的空闲内存块列表。
  4. 线程局部存储(Thread-Local Storage, TLS)

    • 为了减少锁的竞争,每个线程都有一个本地的缓存(mcache)。

内存分配过程

  1. 分配

    • 当一个goroutine需要内存时,它会检查mcache中对应大小等级的空闲列表。
    • 如果mcache没有足够的内存,它会从mcentral获取一个新的内存块。
    • 如果mcentral也没有足够的内存,它会从堆(mheap)中获取一个新的更大的内存块,并将其切分成适当大小的块。
  2. 释放

    • 当对象不再被使用时,它们会被标记为垃圾,等待垃圾回收器来回收。
    • 回收过程中,内存会被返回到相应的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 

区别

  1. 返回类型

    • new(T) 返回的是 *T 类型的指针
    • make(T, args) 返回的是 T 类型的,其中 T 必须是 slice、map 或 channel。
  2. 初始化

    • new 只分配内存,并不初始化内存,内存的值是零值。
    • make 分配并初始化内存,返回的是已经初始化后的值。
  3. 使用场景

    • 对于值类型(如数组、结构体等)使用 new
    • 对于内建类型(如 slice、map、channel)使用 make

与内存分配的联系

  • 堆分配:无论是 new 还是 make,它们分配的内存都是在堆上,这意味着分配的内存直到不再被引用时才会被垃圾回收器回收。

  • 性能考虑:由于 make 还负责初始化内存,因此对于需要立即使用的内建类型来说,使用 make 可以减少后续的初始化步骤,可能带来性能上的优势。

  • 内存管理new 和 make 都是由Go的内存分配器管理的,它们负责从堆上分配内存,并可能涉及Go运行时的内存管理策略,如mcache、mcentral和mheap等。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!