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

相关文章

Java空指针异常NullPointerException的原因与解决方案

《Java空指针异常NullPointerException的原因与解决方案》在Java开发中,NullPointerException(空指针异常)是最常见的运行时异常之一,通常发生在程序尝试访问或... 目录一、空指针异常产生的原因1. 变量未初始化2. 对象引用被显式置为null3. 方法返回null

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

golang float和科学计数法转字符串的实现方式

《golangfloat和科学计数法转字符串的实现方式》:本文主要介绍golangfloat和科学计数法转字符串的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望... 目录golang float和科学计数法转字符串需要对float转字符串做处理总结golang float

golang实现延迟队列(delay queue)的两种实现

《golang实现延迟队列(delayqueue)的两种实现》本文主要介绍了golang实现延迟队列(delayqueue)的两种实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录1 延迟队列:邮件提醒、订单自动取消2 实现2.1 simplChina编程e简单版:go自带的time

Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)

《Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)》本文主要介绍了Golang分布式锁实现,采用Redis+Lua脚本确保原子性,持可重入和自动续期,用于防止超卖及重复下单,具有一定... 目录1 概念应用场景分布式锁必备特性2 思路分析宕机与过期防止误删keyLua保证原子性可重入锁自动

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.

golang中slice扩容的具体实现

《golang中slice扩容的具体实现》Go语言中的切片扩容机制是Go运行时的一个关键部分,它确保切片在动态增加元素时能够高效地管理内存,本文主要介绍了golang中slice扩容的具体实现,感兴趣... 目录1. 切片扩容的触发append 函数的实现2. runtime.growslice 函数gro

golang实现动态路由的项目实践

《golang实现动态路由的项目实践》本文主要介绍了golang实现动态路由项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习... 目录一、动态路由1.结构体(数据库的定义)2.预加载preload3.添加关联的方法一、动态路由1

Golang interface{}的具体使用

《Golanginterface{}的具体使用》interface{}是Go中可以表示任意类型的空接口,本文主要介绍了Golanginterface{}的具体使用,具有一定的参考价值,感兴趣的可以了... 目录一、什么是 interface{}?定义形China编程式:二、interface{} 有什么特别的?✅