25 go语言(golang) - 内存分配机制原理

embedded/2024/12/28 20:49:07/

Go 语言的内存分配机制是一个复杂且高效的系统,旨在为程序提供快速和安全的内存管理。理解 Go 的内存分配有助于编写更高效的代码,并优化程序性能。

一、内存区域

  1. 栈(Stack)

    • 栈用于函数调用时的临时变量分配。
    • 栈上的内存在函数返回后自动释放,因此栈上的分配非常快速。
    • Go 编译器会尽量将局部变量放在栈上,但如果变量需要在函数返回后继续存在,则会被提升到堆上。
    • 每个 goroutine 都有自己的栈,初始大小较小(通常为几 KB),可以动态增长。
  2. 堆(Heap)

    • 堆用于动态内存分配,生命周期由垃圾回收器管理。
    • 在 Go 中,通过 newmake 函数进行显式或隐式堆上对象创建。

示例代码:

package mainimport "fmt"func main() {stackTest()heapTest()
}func stackTest() {stackVar := 1123fmt.Println(stackVar)
}func heapTest() *int {heapVar := 1234fmt.Println(heapVar)return &heapVar
}

变量 stackVar 是一个局部变量,分配在栈上。当 stackTest 函数返回时,stackVar 的内存会自动释放。

当变量需要在函数返回后继续存在时,它们会被分配到堆上,所以变量 heapVar 需要在 heapTest 函数返回后继续存在,Go 编译器通过逃逸分析决定将 heapVar 分配到堆上。

二、内存管理

  1. 逃逸分析(Escape Analysis)

    • 编译器通过逃逸分析决定变量应该放置在栈还是堆上。如果一个变量被认为“逃逸”出其作用域,它将被提升到堆中。
    • 可以使用 go build -gcflags="-m" 来查看编译器的逃逸分析结果。
  2. 垃圾回收(Garbage Collection, GC)

    • Go 使用并发标记-清除算法进行垃圾回收,以减少停顿时间和提高吞吐量。
    • 垃圾回收自动处理不再使用的对象,从而避免手动释放资源导致的问题。
  3. 同步与并发支持

    • Goroutine 是轻量级线程,由运行时调度器管理,通常共享同一地址空间以减少上下文切换开销。

2.1 逃逸分析

使用 go build -gcflags="-m"分析上面代码的逃逸结果

➜ _43memory go build -gcflags="-m" test_main.go
# command-line-arguments
./test_main.go:13:13: inlining call to fmt.Println
./test_main.go:18:13: inlining call to fmt.Println
./test_main.go:13:13: ... argument does not escape
./test_main.go:13:14: stackVar escapes to heap
./test_main.go:17:2: moved to heap: heapVar
./test_main.go:18:13: ... argument does not escape
./test_main.go:18:14: heapVar escapes to heap
  • Inlining call to fmt.Println: 这表明Go编译器尝试内联优化对 fmt.Println 的调用。内联是一种常见的优化技术,可以减少函数调用开销。
  • … argument does not escape: 表示传入 fmt.Println() 的参数没有从该方法中逃逸出去。即使如此,由于可能性和潜在的持久引用问题,“不逃逸”并不意味着不会移动到堆上。
  • stackVar escapes to heap: 尽管参数本身没有从打印方法中直接“逃脱”,但由于某种原因(如下所述),它仍然需要被移到堆上以确保正确性。
  • moved to heap: heapVar: 明确指出由于返回其地址的需求,heapVar 被移动到了堆空间。

为什么 stackVar 会逃逸?

  • stackVar 是一个局部变量,理论上应该在栈上分配。
  • 然而,在编译时发现 fmt.Println(stackVar) 可能需要该变量逃逸到堆上。原因是 fmt.Println 可能会保留对其参数的引用(例如,在内部传递给其他函数或存储于全局变量中),导致 stackVar 需要在函数返回后继续存在。

三、内存池化

sync.Pool 是 Go 语言标准库中的一个缓存池类型,它用于存储和复用临时对象,以减少内存分配和垃圾回收的压力。例如,在频繁创建和销毁大量小对象场景下,可以使用 sync.Pool 来提高性能。

sync.Pool 是一个并发安全的类型,可以在多个 goroutine 之间共享。

3.1 关键特性

  1. 自动垃圾回收

    • sync.Pool 中存储的对象会在不再使用时被垃圾回收器自动清理。这意味着即使你忘记将对象放回池中,也不会导致内存泄漏。
  2. 并发安全

    • sync.Pool 是线程安全的,可以在多个 goroutine 中同时使用,而无需额外同步机制。
  3. 生命周期管理

    • 对象从池中获取后,其生命周期由调用者管理。当调用者不再需要该对象时,应将其放回池中以供后续重用。
  4. 惰性初始化

    • 当从 sync.Pool 获取一个空闲对象时,如果没有可用实例,且定义了 New() 函数,则会调用该函数创建一个新实例。
  5. GC 清理行为

    • 每次进行垃圾回收(GC)时,所有未被引用到外部变量或结构体字段中的 pool 对象都会被清除。这是为了避免长时间持有不必要的数据而导致内存浪费。

3.2 使用方法

type User struct {Id   stringName string
}func Test1(t *testing.T) {// 创建一个对象池,并定义 New 函数pool := sync.Pool{New: func() interface{} {return User{Id:   uuid.New().String(),Name: "A",}},}// 通过 Get() 方法获取对象obj := pool.Get().(User)fmt.Println(obj.Id, obj.Name)// 通过 Put() 方法放回对象pool.Put(obj)objNew := pool.Get().(User)fmt.Println(objNew.Id, objNew.Name)
}

输出

31c09d56-f851-4f08-a151-d340164ca5cb A
31c09d56-f851-4f08-a151-d340164ca5cb A

四、分配策略

4.1 Arena 和 Span

4.1.1 什么是 Span?

  • Span 是一组连续的页(page),这些页被作为一个整体来管理。是内存分配器用来管理堆上内存的一种基本单位。
  • 在 64 位系统上,一个页通常为 8KB,因此一个 span 可以由多个这样的页面组成。
  • Span 用于为特定大小类别的小对象提供内存空间。

4.1.2 什么是 Arena?

  • Arena 是一大片连续的虚拟地址空间,用来为堆上的对象提供基础设施支持。
  • 一个 Arena 包含多个 Span
  • Arenas 提供了一个大的、可控范围,而 Spans 则负责具体的小对象或特定大小类别对象的分配。

4.1.3 Span 分割策略

  1. 按大小类别划分

    • 内存分配器将对象划分为不同的大小类别(size class),每个 size class 对应一组 span。
    • 小对象(通常小于 32KB)会被放置在预先定义好的 size class 中,以便快速查找和复用。
  2. 固定大小块

    • 每个 span 被进一步划分成固定大小的块,这些块用于实际对象的放置。
    • 块的大小取决于它所属的 size class。例如,一个用于 16 字节对象的 span 会被划分成多个 16 字节的小块。
  3. 空闲与使用状态

    • 一个 span 可以处于多种状态:完全空闲、部分使用或完全使用。
    • 当所有小块都被释放后,span 将返回到 mcentral 或 mheap,以供其他请求使用。
  4. 动态调整与合并

    • 如果某个 size class 的需求增加,Go 会从全局堆中获取新的页面,并创建更多相应类型的新 spans。
    • 如果某些 spans 长时间未被使用,它们可能会合并回更大的可用空间,以减少碎片化并提高效率。
  5. 垃圾回收协作

    • 在垃圾回收过程中,会扫描所有活跃 spans 并标记其中仍然有效的数据;
    • 回收无效数据所占据资源,并将其重新加入到 free list 中待后续利用;

4.2 三层架构

Go 语言的内存分配器采用了一个三层架构,包括 mcachemcentralmheap,以高效地管理内存分配和回收。这个架构旨在优化小对象的分配速度,并减少锁竞争,从而提高并发性能。

三层架构可以简单的抽象理解为三层缓存!

4.2.1 mcache

  • 概念

    • mcache 是每个 P(Processor,即 Go 的调度器中的逻辑处理器)私有的缓存,用于快速分配小对象。
  • 作用

    • 提供线程本地化(Thread-local)的内存缓存,以减少全局锁争用,提高小对象分配效率。
  • 工作机制

    • 每个 P 都有一个独立的 mcache,用于缓存从 mcentral 获取的小块内存(span)。
    • 当 goroutine 请求一个小对象时,首先尝试从其所在 P 的 mcache 中获取。如果没有可用空间,则从 mcentral 获取新的 span。

4.2.2 mcentral

  • 概念

    • mcentral 是多个 P 共享的中央缓存,用于管理相同大小类的小块内存。
  • 作用

    • 管理和组织不同大小类的小块内存,为各个 P 的 mcache 提供资源。
  • 工作机制

    • 按照不同大小类别组织 span,每种大小类别都有对应的 free list。
    • 当某个 P 的 mcache 缺少特定大小类别时,会向对应的 mcentral 请求新的 span。

4.2.3 mheap

  • 概念

    • 全局堆,是整个程序共享的一大片虚拟地址空间,用于大规模或长生命周期数据块请求来源地。
  • 作用:

    • 管理所有空闲的大型数据块和未使用页;
    • 为上层提供基础设施支持;
  • 工作机制

    • 在需要大型数据块或者无法通过上两级满足请求情况下,直接与操作系统交互申请更多物理页;
    • 将新申请到物理页划分成更细粒度单位并加入到相应 free list 中待后续使用;

4.2.4 内部运作流程

  1. 当一个 goroutine 发起一次小型对象请求时,它会首先检查当前所属 Processor 上是否存在足够容量可用自有资源池中;

  2. 如果找不到合适位置,则向中央资源池发起进一步请求以获取所需类型 Span;

  3. 若仍然无法满足需求则最终通过全局堆与底层操作系统进行直接交互来扩展整体虚拟地址空间范围;

4.3 小对象大对象

4.3.1 小对象

  • 范围一般指大小在 16 字节到 32KB 之间的对象。
  • 分配策略
    • 小对象通过 mcachemcentral 来进行快速分配。
    • 使用固定大小类别(size class)来组织这些小型数据块,每个 size class 对应一个特定大小的 span。
    • 当请求一个小型数据块时,Go 会从相应 size class 的 span 中获取可用空间。

4.3.2 大对象

  • 范围:指大小大于 32KB 的对象。
  • 分配策略
    • 大对象直接从全局堆(mheap)中进行分配,而不是通过 mcache 或 mcentral。
    • 因为大对象可能会跨越多个页面,因此需要专门处理以确保它们能够被高效地回收和重用。
    • 由于可能并发访问,所以通常需要加锁获取。

http://www.ppmy.cn/embedded/149550.html

相关文章

Java高级工程师1380道面试题(附答案)分享

Java 面试八股文有必要背吗? 我的回答是:很有必要。你可以讨厌这种模式,但你一定要去背,因为不背你就进不了大厂。现如今,Java 面试的本质就是八股文,把八股文面试题背好,面试才有可能表现好。…

pinia从0到1

一、创建项目 1. npm create vitelatest 2. 输入项目名称 3. cd 到新建的项目 4. npm install 安装项目依赖 5. npm run dev 运行项目 二、安装Pinia npm install pinia三、在main.js中挂载 1.引入pinia import {createPinia} form “pinia”; 2.创建pinia对象 const pinia …

react里使用Day.js显示时间

介绍 官方文档: https://dayjs.fenxianglu.cn/ 安装方式 npm install dayjscnpm install dayjsyarn add dayjspnpm add dayjs 引入及使用 // 引入 var dayjs require(dayjs); // 或者 // import dayjs from dayjs // 使用 // 打印当前时间对象的格式化时间 c…

Java字面量详解:概念、分类与使用实例

目录 什么是Java字面量? 字面量的分类 1. 整数字面量 示例: 2. 浮点数字面量 示例: 3. 字符字面量 示例: 4. 布尔字面量 示例: 5. 字符串字面量 示例: 6. 空字面量(null&#xff0…

如何安全获取股票实时数据API并在服务器运行?

以下是安全获取股票实时数据 API 并在服务器运行的方法: 选择合适的券商或交易平台 评估自身需求:明确自己的交易策略、交易品种、交易频率等需求,以及对 股票api 的功能、性能、稳定性等方面的要求。调研券商或平台:了解不同券商…

3D架构图软件 iCraft Editor 正式发布 @icraftplayer-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生

介绍 icraft/player-react 是 iCraft Editor 全新推出的 React 组件库,专为简化3D数字孪生场景的前端集成而设计。通过该组件,开发者可以轻松地将 iCraft Editor 制作的3D场景无缝嵌入到 React 项目中,并获得丰富的交互能力和实时数据集成特…

Keras2.0 ImageDataGenerator 适配

最近在学习keras,总遇到使用 ImageDataGenerator当作训练参数,使用 fit_generator(), 而 Keras 2.0 已经放弃了,导致执行不下去了 经过N多天的摸索,终于是成功了 # 训练集数据生成 datagen ImageDataGenerator(rotation_range40,rescale…

输入框去掉角标

前言 正常情况下&#xff0c;HTML textarea 多行文本输入框会存如下图所示图标&#xff0c; 用户可拉动它改变高度&#xff0c;这是我们不想看到的&#xff0c;所以要去掉它。 去掉后&#xff1a; 解决方案 设置 resize 属性即可&#xff0c;如下代码所示&#xff1a; <…