当前位置: 首页 > ai >正文

Golang 内存模型小结

Go 的内存模型

Go 的内存模型描述了如何分配内存、访问内存以及内存共享等细节。Go 程序的内存管理主要依赖如下方面:

  • 堆内存(Heap Memory)。用于存放程序运行时创建的对象,由 Go 的垃圾回收器自动管理。堆内存的生命周期不由函数作用域决定,而是由对象引用来决定。
  • 栈内存(Stack Memory)。用于存放局部变量、函数参数等数据。生命周期与函数调用栈相关。栈的管理非常高效,因为栈空间是先进后出(LIFO)的结构,而且通常是由操作系统直接管理。
  • 堆栈分配(Heap-Stack Allocation)。Go 语言在运行时会决定对象应该分配堆上还是栈上,具体是由基于逃逸分析的结果来确定。

Go 的内存分配过程

Go 的内存分配由内存分配器负责,内存分配器的核心任务是从操作系统请求内存、将其分配给相应的 Go 程序使用、管理内存的回收。

内存分配过程概述

  1. 栈分配

    如果局部变量和参数不会逃逸,即在函数返回时不再使用,则它们会分配在栈上。栈的分配非常高效,由操作系统直接管理内存并自动回收。

    栈上的对象生命周期与函数调用相同,当函数返回时,栈上的所有局部变量都将被销毁。

  2. 堆分配

    当一个变量逃逸,即变量的生命周期超出了函数作用域,将被分配在堆上。堆上的对象生命周期不由栈帧的生命周期决定,而是由 GC 来管理。

    堆上的对象生命周期由 GC 决定,对象是否被回收取决于对象是否还有引用指向它。

  3. 逃逸分析

    Go 语言的编译器会对代码进行逃逸分析,以决定哪些变量应该分配在堆上,哪些变量应该分配在栈上。逃逸分析的目的是为了尽可能地避免不必要的堆分配,提高性能。

    如果一个局部变量的地址被返回或者传递给了全局变量,它就会逃逸到堆上;如果变量只是局部使用,并且没有返回其地址,它就不会逃逸。

// x 是局部变量,原本应该分配在栈上,但由于 foo 函数返回 x 的地址,x 的生命周期被延长,Go 编译器随即将它分配到堆上。
func foo() *int {// 栈分配x := 40// x 逃逸到堆return &x
}func main() {y := foo()fmt.Println(*y)
}

Go 的垃圾回收机制

Go 语言采用垃圾回收机制来管理堆内存的回收,GC 的主要任务是自动检测不再使用的对象并将其回收,从而避免内存泄漏。

GC 的工作原理

Go 的垃圾回收器使用标记-清扫算法

  1. 标记阶段

    GC 从根对象(比如全局变量、栈变量)开始,递归标记所有仍然在使用的对象。

  2. 清扫阶段

    一旦标记完成,GC 会清理掉没有被标记的对象,并释放它们占用的内存。

Go 的垃圾回收器是并行和分代的,在分配内存时,它会尽可能地避免长时间的停顿,这对于高性能应用程序而言至关重要。

  • 增量式 GC。Go 的垃圾回收是增量式的,即它会将垃圾回收的工作分成多个小步骤,避免一次性停顿。
  • 并行 GC。Go 的垃圾回收是并行的,它会利用多核处理器的优势,同时进行多个 GC 任务。
  • 分代 GC。Go 的垃圾回收采用分代的策略,即将年轻代和老年代分开管理,年轻代对象更容易回收,而老年代对象的回收频率较低。

GC 的停顿时间

尽管 Go 的垃圾回收器非常高效,但在 GC 过程中,仍然会产生停顿。Go 1.5+ 在GC 算法上进行了改进,减少了停顿时间。

  • GC 时间。GC 的停顿时间通常是短暂的,但是对于实时性要求高的系统来说,可能仍然需要进行调优。
  • 调优 GC。可以通过环境变量 GOGC 来调整 GC 的触发阈值。GOGC 值决定了垃圾回收器的触发时机,默认值是 100,表示当堆内存增长到原来的一倍时,便触发 GC。

此外,Go 语言提供了 pprof 工具,可以用于分析 Go 程序性能,通过 pprof,可以查看 GC 的运行情况,分析垃圾回收的时间消耗和内存使用情况;runtime/pprof 包提供了获取程序运行时的性能信息的功能,可以用于分析内存分配的情况。

Go 的内存泄漏

分析:

  • 未关闭的资源。比如未关闭的文件、数据库连接等。
  • 循环引用。多个对象相互引用,导致 GC 无法回收。
  • 持久化引用。通过全局变量或者长生命周期的对象保持对不再使用的对象的引用。

处理:

  • 使用 defer 关闭资源。确保在使用完资源后及时关闭它们。
  • 避免循环引用。确保对象间的引用不会形成循环。
  • 合理管理全局变量。避免全局变量持有对不再使用的对象的引用。

Go 的可见性和重排序

在 Go 中,多个 goroutine 并发访问共享变量时,如果不通过同步原语(如 Channel、锁、atomic 操作)进行同步,则变量的读写操作是没有顺序性和一致性保证的。Go 编译器和底层 CPU 可能会对指令进行重排序,以提高执行效率,但这会导致实际运行时的指令顺序与源码不一致,从而引发并发错误。如果希望一个 goroutine 中写入的值能被另一个 goroutine 正确读取,就必须使用同步原语来建立同步关系。

// 在没有同步的前提下,thread2 可能会输出:b = 1 而 a = 0
var a, b intfunc thread1() {a = 1b = 1
}func thread2() {fmt.Println(b)fmt.Println(a)
}
  • 编译器或 CPU 重排序。thread1 中的 a = 1 和 b = 1 两行代码在源码中是有先后顺序的,但为了优化性能,编译器或 CPU 可能会将它们重排序为先执行 b = 1,再执行 a = 1,因为它们之间没有依赖关系。
  • 内存可见性问题。即使在 thread1 的执行顺序是 a = 1; b = 1,由于没有同步,thread2 可能在写入 a 之前就观察到了写入 b,因为缓存同步机制、CPU 内存模型等原因,导致一个 goroutine 对变量的修改对另一个 goroutine 不可见。
http://www.xdnf.cn/news/7970.html

相关文章:

  • Qt 最新版6.9.0使用MQTT连接腾讯云详细教程
  • window 显示驱动开发-视频内存供应和回收(一)
  • jquery.table2excel方法导出
  • 鸿蒙仓颉开发语言实战教程:实现商城应用首页
  • 尼科彻斯定理
  • Vue 3.0中自定义指令
  • 01-jenkins学习之旅-window-下载-安装
  • 【云原生安全】零信任与机密计算
  • Qt C++实现马的遍历问题
  • 【JavaEE】(1) 计算机如何工作
  • 阿里巴巴 MCP 分布式落地实践:快速转换 HSF 到 MCP server
  • 记录:uniapp 上线部署到微信小程序vendorjs包过大的问题
  • 外网如何连接内网中的mysql数据库服务器?简单网络工具方案
  • Cause: org.apache.ibatis.ognl.OgnlException: sqlSegment
  • 【C++】位图+布隆过滤器
  • JAVA EE(进阶)_CSS
  • 如何排查服务器 CPU 温度过高的问题并解决?
  • C++ 前缀和数组
  • C++STL(二)类模板
  • YCKC【二分查找专题】题解
  • 《对话记忆的进化史:智能体大模型如何实现跨轮次的深度交互》
  • 国酒华夏实业酒水供应链:全品类覆盖打造一站式购销平台
  • 第四十三节:人脸检测与识别-人脸识别基础 (Eigenfaces, Fisherfaces, LBPH)
  • Selenium自动化测试终极指南:从原理到实战
  • 【Python生成器全解析】从基础到高阶应用实战
  • C语言—Linux环境下CMake设置库(动态/静态)
  • 借助IEDA ,Git版本管理工具快速入门
  • 多线程(七)
  • 开疆智能Profinet转RS485网关连接工业型土壤水分温度传感器 配置案例
  • 如何在 Windows 10 或 11 上安装 Adminer?