【Go系列】Go的内存分配

承上启下

在上一篇文章中,我们介绍了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等。

相关推荐
紧跟先前的步伐34 分钟前
Go AI Agent 实战 04|Prompt 与消息角色:System、User、Assistant
人工智能·golang·prompt
小oo呆1 小时前
【学习心得】Python函数参数中的两个特殊分隔符
开发语言·python
x10n91 小时前
微软 VC++ 运行库合集VCRedistPack
开发语言·c++·microsoft·vc++运行库·dll报错
qz_Serene1 小时前
C++:string(底层,常用接口,具体题目,模拟实现)
开发语言·c++
NiceBlueChai1 小时前
Qt 跨线程传递多容器嵌套结构:一次 Release 实测
开发语言·qt
念何架构之路1 小时前
go-grpc逻辑架构图
开发语言·后端·golang
SEO_juper1 小时前
AI 爬虫 robots.txt 检查工具:你的站是不是把 AI 挡在门外了
开发语言·人工智能·爬虫·python·外贸独立站
木圭的AI时代指南1 小时前
实测 MiniMax M3.1 Flash:同一道 Three.js 大题,两个 agent 差出 4.6 倍
开发语言·javascript·ecmascript
星恒讯工业路由器1 小时前
工业路由器在中小 IoT 场景的选型与适配实践:从标准品到定制化的技术路径
开发语言·网络·物联网·php·工业路由器选型·工业网关odm·链路冗余切换