Golang内存分配机制详解

 更新时间:2023年12月20日 08:59:30   作者:路多辛  
Go 语言的内存分配机制是理解和优化 Go 程序性能的关键,在 Go 中,内存管理是自动进行的,这得益于 Go 的垃圾回收机制,了解内存如何分配和回收,可以帮助我们写出更高性能的代码,本文将深入讲解下 Go 内存分配机制,需要的朋友可以参考下

内存分配的基本原理

在计算机科学中,内存分配是指为程序中的变量和数据结构分配存储空间的过程。在 Golang 中,内存分配主要由 Go 运行时系统(runtime)负责,主要包括以下两个方面:

  • 堆内存分配:堆内存是用于动态分配的内存区域,主要用于存储程序运行过程中创建的对象和数据结构。通过 new、make 等函数或者使用指针进行内存分配时,都会在堆上分配内存。
  • 栈内存分配:栈内存是用于存放函数调用时的局部变量和返回地址等信息的内存区域。基本类型(如整型、浮点型、布尔型等)和小对象(通常小于 64 字节)的内存分配通常发生在栈上。

Go 使用了一个称为“tcmalloc”(thread-caching malloc)的内存分配器,最初由 Google 开发。tcmalloc 的设计目标是减少全局锁的竞争,提高多线程程序的性能。Go 的内存分配器是基于 tcmalloc 概念的一个实现,有几个关键的组成部分:

  • M: 代表操作系统线程(machine)。
  • P: 代表处理器(processor),管理着一组本地缓存。
  • G: 代表 goroutine,是 Go 程序执行的最小单位。

每个 P 都有自己的内存缓存(mcache),用于小对象的快速分配。当 mcache 用完时,P 会从中心缓存(mcentral)获取内存。如果 mcentral 也不足,内存分配器会向操作系统请求更多内存。

Golang 内存分配的机制

  • 小对象分配,小对象(一般小于 32KB)的分配是通过 P 的 mcache 来进行的。mcache 包含了一系列固定大小的内存块,称为“span”。每个 span 专门用于一种大小的对象。当一个 goroutine 需要分配一个小对象时,就会查找对应大小的 span,并从中分配一块内存。
  • 大对象分配,大对象(一般大于 32KB)的分配不经过 mcache,而是直接从堆上分配。这是因为大对象的分配和回收比小对象更少,直接在堆上操作可以减少碎片和管理的复杂性。
  • 内存分配优化,为了减少内存分配的成本,Go 的内存分配器会进行一些优化:
    • 大小类分配:为了减少内存碎片和提高内存重用,Go 将对象分为不同的大小类。每个大小类的对象都会分配到对应的 span 中。
    • 对象对齐:Go 保证对象在内存中对齐,有助于提高 CPU 缓存的效率。
    • 批量分配:当 mcache 中的 span 用完时,不是一次只获取一个新的 span,而是批量地从 mcentral 获取多个 span,可以减少与 mcentral 的交互次数。

垃圾回收(GC)

Go 的垃圾回收器是一个并发的、标记-清除(mark-sweep)垃圾回收器。垃圾回收分为几个阶段:

  • 标记阶段:垃圾回收器会停止所有的 goroutine(STW - stop the world),快速扫描栈和全局变量,标记所有可达的对象。
  • 并发标记:goroutine 被恢复执行,同时垃圾回收器在后台并发地完成标记工作。
  • 清除阶段:清除未被标记的对象,通常也是并发进行的。

Go 的垃圾回收器设计为低延迟,会尽量减少对程序执行的干扰。

内存逃逸

在 Go 中,编译器会尽量在栈上分配内存,因为栈上的内存分配和回收非常快。然而,并不是所有的内存分配都可以在栈上完成。当编译器无法保证对象的生命周期仅限于其定义的作用域时,会将这些对象分配到堆上,这个过程称为“内存逃逸”。

内存分配的影响因素

内存分配的性能可能受到多种因素的影响,包括以下几个方面:

  • 内存分配频率:频繁的内存分配和回收会增加垃圾回收器的工作量,从而影响性能。
  • 对象大小:大对象的分配通常比小对象慢,因为不经过 mcache。
  • 对象生命周期:长生命周期的对象可能会导致内存占用增加,因为不会被频繁回收。

内存分配的最佳实践

为了优化内存分配,可以从以下几个方面着手:

  • 重用对象:通过重用对象来减少分配次数。
  • 池化资源:使用 sync.Pool 来池化可重用的对象。
  • 避免内存逃逸:通过减少指针的使用和闭包捕获来避免不必要的内存逃逸。
  • 合理的数据结构:选择合适的数据结构来减少内存的占用和碎片。

小结

Go 的内存分配器是为并发和多线程设计的,通过一系列优化来提供高效的内存分配。内存分配的性能不仅取决于分配器本身,还取决于程序的设计和编码方式。在日常开发中,可以使用工具(如 pprof)来分析和优化程序的内存使用情况。通过实践和分析,更深入地理解和掌握 Go 的内存管理机制。

以上就是Golang内存分配机制详解的详细内容,更多关于Golang内存分配机制的资料请关注脚本之家其它相关文章!

相关文章

  • Go通用的 MapReduce 工具函数详解

    Go通用的 MapReduce 工具函数详解

    本文介绍了使用Go语言实现的MapReduce框架,特别是在AWSS3 SDK的MultiPartUpload功能中的应用,包括并发上传和错误处理策略,详细解释了如何通过并发goroutines提高上传效率,并通过MapReduce模型优化代码结构和处理流程,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • GoLang unsafe包详细讲解

    GoLang unsafe包详细讲解

    从golang的定义来看,unsafe 是类型安全的操作。顾名思义,它应该非常谨慎地使用; unsafe可能很危险,但也可能非常有用。例如,当使用系统调用和Go结构必须具有与C结构相同的内存布局时,您可能别无选择,只能使用unsafe
    2022-10-10
  • Golang通道的无阻塞读写的方法示例

    Golang通道的无阻塞读写的方法示例

    这篇文章主要介绍了Golang通道的无阻塞读写的方法示例,详细的介绍了哪些情况会存在阻塞,以及如何使用select解决阻塞,非常具有实用价值,需要的朋友可以参考下
    2018-11-11
  • 浅析Go语言中的缓冲区及其在fmt包中的应用

    浅析Go语言中的缓冲区及其在fmt包中的应用

    这篇文章主要为大家详细介绍了Go语言中的缓冲区及其在fmt包中的应用的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2024-01-01
  • Golang 使用接口实现泛型的方法示例

    Golang 使用接口实现泛型的方法示例

    这篇文章主要介绍了Golang 使用接口实现泛型的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Golang之sync.Pool使用详解

    Golang之sync.Pool使用详解

    这篇文章主要介绍了Golang之sync.Pool使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 在Golang代码中如何自动生成版本号的方法示例

    在Golang代码中如何自动生成版本号的方法示例

    这篇文章主要给大家介绍了在Golang代码中如何自动生成版本号的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-07-07
  • Go mod replace使用方法及常见问题分析

    Go mod replace使用方法及常见问题分析

    这篇文章主要为大家介绍了Go mod replace使用方法及常见问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Go语言处理超大字符串型整数加减经典面试详解

    Go语言处理超大字符串型整数加减经典面试详解

    这篇文章主要为大家介绍了Go语言处理超大字符串型整数加减经典面试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Golang实现Redis过期时间实例探究

    Golang实现Redis过期时间实例探究

    这篇文章主要介绍了Golang实现Redis过期时间实例探究,
    2024-01-01

最新评论