Golang内存、指针逃逸、垃圾回收机制概览

2024-04-19 23:12

本文主要是介绍Golang内存、指针逃逸、垃圾回收机制概览,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近看到了一篇文章是关于go的内存、指针逃逸和垃圾回收机制的,发现自己并未很细致的了解过这方面的内容,于是在翻阅各种文章的情况下,写出了这篇总结,参考文章放在文末,可自取

内存

Go 语言使用一个自带的垃圾收集器(Garbage Collector, GC)来自动管理内存,这意味着程序员不需要直接参与内存的分配和释放,这减少了内存泄漏和其他内存相关错误的可能性。Go 中的内存可以分为两个主要部分:

  1. 栈(Stack):栈通常存储大小生命周期是能被预估的数据。函数内的局部变量和返回值;管理采用先进后出的模式,不需要复杂的垃圾回收机制;栈的特点是拥有非常高的访问速度和较低的内存分配开销,但空间有限。
  2. 堆(Heap):用于存储运行时可能变化的数据,或函数作用域之外需要访问的数据;更大规模的内存区域,用于存储生命周期较长或大小无法预知的数据。堆内存的分配和回收成本相对较高,但可以动态地扩展。

可以通过以下命令来分析应用程序:

go build -gcflags=-m main.go

指针逃逸

指针逃逸分析是 Go 编译器进行的一种优化。通过这种分析,编译器确定变量的存储位置(栈还是堆)。如果一个变量在函数结束后仍然可以被访问(例如,被其他函数引用或返回给调用者),这个变量就会从栈“逃逸”到堆。

指针逃逸的主要影响是性能:

  • 栈分配的变量:当函数调用结束时,这些变量的内存可以立即被清理,这一过程非常快速且高效。
  • 堆分配的变量:需要垃圾收集器介入来回收这部分内存,这可能导致额外的性能开销。

深入理解指针逃逸

在 Go 中,编译器进行逃逸分析是为了决定数据应当存放在堆上还是栈上。我们已经知道,存放在栈上的数据有着更快的访问速度和更简单的生命周期管理,但栈的空间有限且仅在函数执行期间存在。相反,堆上的数据可以在函数执行完毕后继续存在,但其管理成本较高,因为涉及到复杂的垃圾回收机制。

何时发生逃逸?

  1. 返回局部变量的地址:如果函数返回局部变量的指针,这个变量就会从函数的栈帧中逃逸到堆,因为局部变量的生命周期必须延长到函数外部。
  2. 大对象:即使对象没有被外部引用,如果对象非常大,它可能也会被分配到堆上,以避免栈溢出。
  3. 动态类型:如接口或含有接口的类型。由于接口的动态特性,编译器可能无法预测具体的实现类型和大小,因此可能选择将其分配到堆上。
  4. 闭包:引用外部函数局部变量的闭包可能导致这些变量逃逸,因为这些变量必须在闭包存在时继续存在。

优化技巧

理解和优化指针逃逸可以使得 Go 程序更加高效。以下是一些常见的优化技巧:

  • 避免不必要的堆分配:尽量使用局部变量和传值,避免在不必要的情况下创建指针。
  • 使用对象池:对于频繁使用和创建的对象,可以使用 sync.Pool 来复用对象,减少垃圾收集的负担。
  • 分析逃逸情况:使用 go build -gcflags="-m" 命令来查看编译器的逃逸分析结果,了解哪些变量逃逸到堆,并探索优化方法。
  • 配置垃圾收集器:通过设置 GOGC 环境变量(默认值是 100),可以调整垃圾收集器的敏感度。增加这个值会减少垃圾收集的频率,可能增加程序的整体内存使用,但可以减少因垃圾收集引起的延迟。

垃圾回收机制

Go语言的垃圾回收(GC)机制是一种自动内存管理的实现,它旨在帮助程序开发者免除手动管理内存的复杂性。Go的垃圾回收器主要基于“标记-清扫”(Mark-and-Sweep)算法,但随着版本的更新,Go团队已经对其进行了优化和改进,引入了并发的执行和更多的性能优化措施。Go的GC实现的特点是并发执行,且尽量减少对程序执行的干扰。

overview

设计原则

Go的垃圾回收器设计目标是简化并发程序的内存管理,同时实现以下几个关键目标:

  1. 效率:尽量减少GC的CPU和内存开销。
  2. 并发:GC过程与用户程序并发执行,减少STW(Stop-The-World)的影响。
  3. 实时性:保证程序的响应时间,通过减少GC引起的延迟。

垃圾回收器

垃圾回收器中的变量通常分为以下三类:

  • 活动堆内存(在上一次垃圾回收周期中标记为“活动”的内存)
  • 新堆内存(尚未由垃圾回收器分析的堆内存)
  • 内存用于存储一些元数据,通常与前两个实体相比微不足道。

垃圾回收器的CPU时间消耗与其工作特性有关。有一种称为“全停顿”的垃圾回收器实现,它会在垃圾回收期间完全停止程序执行,导致CPU时间用于非生产性工作。

在Go的情况下,垃圾回收器并非完全“全停顿”,并且在应用程序执行过程中并行执行大部分工作,例如堆标记。

然而,垃圾回收器仍然有一些限制,并且在一个周期内多次完全停止执行工作代码。

垃圾收集的性能开销和内存使用效率直接关联到逃逸分析的结果。减少堆分配可以显著降低垃圾收集的频率和延迟,从而提高程序的整体性能。

核心算法

Go 的垃圾收集器是一个实现了三色标记清除算法的并发收集器。垃圾收集过程主要分为以下几个阶段:

初始化阶段

GC的启动通常由内存分配触发,当分配的总内存量达到当前堆大小的一定比例(由**GOGC**环境变量控制,默认为100%)时,GC开始工作。

标记阶段(Mark Phase)

在这一阶段,垃圾回收器通过从根对象(如全局变量和当前所有Goroutine的栈)出发,标记所有可达的对象。Go使用写屏障(write barrier),在运行时对对象进行标记,这有助于垃圾回收器在应用程序运行时并发执行。

  • 三色抽象:使用黑色、灰色和白色来代表不同状态的对象:
    • 黑色:对象及其子对象都已经被扫描,不会再引用新的白色对象。
    • 灰色:对象被标记为存活,但其子对象还未扫描完。
    • 白色:对象未被访问,可能是垃圾。

gif

清扫阶段(Sweep Phase)

标记完成后,GC进入清扫阶段。在这个阶段,GC遍历堆中的所有对象,释放那些标记为白色的对象所占用的内存。清扫阶段通常也是并发进行,不会中断程序的正常执行。

如何管理垃圾回收器

有一个参数允许您在Go中管理垃圾回收器:GOGC环境变量或其功能等效项SetGCPercent,来自runtime/debug包。

GOGC参数决定了在触发垃圾回收时相对于活动内存的新未分配堆内存的百分比。

GOGC的默认值为100,这意味着当新内存的数量达到活动堆内存的100%时,将触发垃圾回收。

优化和改进

并发垃圾回收

Go的GC从版本1.5开始实施并发标记,这显著降低了STW的时间。在最近的版本中,Go团队进一步减少了GC操作中必须停止程序执行的时间。

写屏障

写屏障是用来维护GC标记正确性的技术。当程序运行时修改对象引用时,写屏障确保这些改动不会破坏正在进行的垃圾回收过程。Go使用的是混合写屏障,它在GC期间启用,有助于标记阶段的并发执行。

调节和配置

  • GOGC环境变量:通过设置这个环境变量,开发者可以控制GC触发的频率。增大这个值会增加堆的允许大小,从而减少GC的频率,反之亦然。
  • runtime/debug 包:提供了更细粒度的控制,比如**SetGCPercent**函数允许在运行时调整GC的触发阈值。

性能考量

尽管Go的GC是高度优化的,但在内存密集或延迟敏感的应用中,GC仍可能成为性能瓶颈。开发者需要通过剖析工具(如pprof)定期检查GC的性能影响,并适当调整GC配置以优化应用性能。

参考文章

  1. Golang垃圾回收(GC)介绍
  2. Memory Optimization and Garbage Collector Management in Go

这篇关于Golang内存、指针逃逸、垃圾回收机制概览的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/918698

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

Spring中事务的传播机制

一、前言 首先事务传播机制解决了什么问题 Spring 事务传播机制是包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。 事务的传播级别有 7 个,支持当前事务的:REQUIRED、SUPPORTS、MANDATORY; 不支持当前事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务传播级别。

C语言入门系列:探秘二级指针与多级指针的奇妙世界

文章目录 一,指针的回忆杀1,指针的概念2,指针的声明和赋值3,指针的使用3.1 直接给指针变量赋值3.2 通过*运算符读写指针指向的内存3.2.1 读3.2.2 写 二,二级指针详解1,定义2,示例说明3,二级指针与一级指针、普通变量的关系3.1,与一级指针的关系3.2,与普通变量的关系,示例说明 4,二级指针的常见用途5,二级指针扩展到多级指针 小结 C语言的学习之旅中,二级

Android SurfaceFlinger——图形内存分配器(十一)

前面的文章中的图层合成器(HWC),这里我们接着看一下 SurfaceFlinger 中的另一个重要服务——图形内存分配器。 一、简介         android.hardware.graphics.allocator@2.0 是 Android 系统中硬件抽象层(HAL)的一个组件,专门用于图形内存的分配和管理。它是 SurfaceFlinger 在处理图形数据时所依赖的

利用结构体作为函数参数时结构体指针的定义

在利用结构体作为函数的参数进行传递时,容易犯的一个错误是将一个野指针传给函数导致错误。 #include <stdio.h>#include <math.h>#include <malloc.h>#define MAXSIZE 10typedef struct {int r[MAXSIZE]; //用于存储要排序的数组,r[0]作为哨兵或者临时变量int length;

逆向学习汇编篇:内存管理与寻址方式

本节课在线学习视频(网盘地址,保存后即可免费观看): ​​https://pan.quark.cn/s/3ceeb9ae6d98​​ 在汇编语言的世界中,内存管理和寻址方式是构建程序的基础。理解这些概念不仅对于编写高效的汇编代码至关重要,也是进行逆向工程分析的关键技能。本文将深入探讨内存管理的基本原则和多种寻址方式,并通过代码案例来展示它们的实际应用。 1. 内存管理 内存管理涉及如何分配

isa指针的理解

D3实例isa指向D3类对象。D3类的话isa指向D3元类对象。D3元类保存类中的方法调度列表,包括类方法和对象方法

段,页,段页,三种内存(RAM)管理机制分析

段,页,段页         是为实现虚拟内存而产生的技术。直接使用物理内存弊端:地址空间不隔离,内存使用效率低。 段 段:就是按照二进制文件的格式,在内存给进程分段(包括堆栈、数据段、代码段)。通过段寄存器中的段表来进行虚拟地址和物理地址的转换。 段实现的虚拟地址 = 段号+offset 物理地址:被分为很多个有编号的段,每个进程的虚拟地址都有段号,这样可以实现虚实地址之间的转换。其实所谓的地