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

devtools/2024/9/18 12:54:20/ 标签: 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

http://www.ppmy.cn/devtools/14021.html

相关文章

Laravel 6 - 第十六章 Artisan命令

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

前缀和 求数列的子序列的K倍区间

(直接截图比复制文字要好多了) 不会做的时候我去看了之前做的关于这道题目的笔记, (Ak 1)% k 1 (Ak 1 Ak)% k 1 只要发现了同余数的情况就说明有一个区间满足了题目的要求。 这个方法的精妙之处就在于前缀和包括了…

强力的应用容器引擎---------Docker的资源控制

目录 一、CPU 资源控制 1.1cgroups有四大功能 1.2设置CPU使用率上限 1.2.1查看CPU使用率 1.2.2进行CPU压力测试 1.2.3设置50%的比例分配CPU使用时间上限 1.3设置CPU资源占用比(设置多个容器时才有效) 1.3.1创建两个容器为hua1 和hua2&#xff0c…

Python基础03-深入探索Python字典操作

在Python中,字典是一种非常强大和灵活的数据结构,可以存储键值对,并提供了许多方法来操作这些键值对。本文将深入探讨Python字典的各种操作,包括如何创建、修改、合并和查找字典中的元素。 1. 创建字典 要创建一个字典&#xff…

基于springboot实现的摄影跟拍预定管理系统

开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven…

apiFox如何批量删除接口?

自动鼠标控制项目 项目简介 本项目通过Python脚本实现了自动化控制鼠标的功能。包括自动获取鼠标坐标和在特定坐标上执行点击操作。这些脚本可以用于自动化测试、游戏作弊、自动化办公等场景。 文件说明 项目包含以下两个主要的Python脚本: getZuoBiao.py - 该…

电力作业平台车必备:防倾倒预警装置,智能守护你的工作

引言 在电力作业中,平台车作为一种重要的高空作业设备,广泛应用于线路检修、设备维护等工作场景。然而,平台车在高空作业过程中存在的倾倒风险,一直是困扰作业人员的难题。为了有效预防此类事故的发生,防倾倒预警装置…

ElasticSearch(3)

目录 126.ES聚合中的Metric聚合有哪些?如何解释? 127.ES聚合中的管道聚合有哪些?如何理解? 128.如何理解ES的结构和底层实现? 129.ES内部读取文档是怎样的?如何实现的? 130.ES内部索引文档是怎样的?如何实现的?</

easyexcel升级3.3.4失败的经历

原本想通过easyexcel从2.2.6升级到3.3.3解决一部分问题&#xff0c;结果之前的可以用的代码&#xff0c;却无端的出现bug 1 Sheet index (1) is out of range (0…0) 什么都没有改&#xff0c;就出了问题&#xff0c;那么问题肯定出现在easyexcel版本自身.使用模板填充的方式进…

描述一下Java中的RMI(远程方法调用)

Java远程方法调用&#xff08;Remote Method Invocation&#xff0c;简称RMI&#xff09;是一种允许在网络中的不同Java虚拟机&#xff08;JVM&#xff09;之间进行通信和调用远程对象上的方法的机制。RMI是Java中实现分布式计算的核心技术之一&#xff0c;它使得开发者可以创建…

设计模式-00 设计模式简介之几大原则

设计模式-00 设计模式简介之几大原则 本专栏主要分析自己学习设计模式相关的浅解&#xff0c;并运用modern cpp 来是实现&#xff0c;描述相关设计模式。 通过编写代码&#xff0c;深入理解设计模式精髓&#xff0c;并且很好的帮助自己掌握设计模式&#xff0c;顺便巩固自己的c…

LeetCode 0216.组合总和 III:回溯(剪枝) OR 二进制枚举

【LetMeFly】216.组合总和 III&#xff1a;回溯(剪枝) OR 二进制枚举 力扣题目链接&#xff1a;https://leetcode.cn/problems/combination-sum-iii/ 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返…

全世界IT人苦竞业久矣!美国FTC宣布全面废除员工竞业协议

2023 年 1 月&#xff0c;美国联邦贸易委员会&#xff08;FTC&#xff09;发布声明称&#xff0c;拟在全国范围禁止用人单位与雇员签订竞业禁止性条款。当地时间 4 月 23 日&#xff0c;FTC 宣布全面禁止所有员工&#xff08;包括高级管理人员&#xff09;签署新的竞业禁止协议…

光接入网络的超宽带半导体光放大器

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 新颖的双有源层结构获得宽增益光谱&#xff0c;应用于多波单纤双向光放大 ----翻译Xiao Sun等人2016年撰写的文章&#xff0c;文中给出了宽光谱SOA的一种新颖的结构设计方法和仿真结果&#xff0c;但并未给…

中仕公考:教师的工资组成是怎样的?

1、基本工资 ① 岗位工资&#xff0c;这部分工资是和职称挂钩的&#xff0c;职称越高工资越高; ② 教龄工资&#xff0c;这部分工资会随着教龄增长而逐步增长; 2、绩效工资: 基础性绩效(看职称) 奖励性绩效: 班主任及领导津贴、教龄津贴、课时津贴、考勤津贴、精神文明奖…

Web前端 Javascript笔记5

1、事件类型 ①、HTML事件 事件说明load当页面完全加载后在window上面触发&#xff0c;或当框架集加载完毕后在框架集上触发&#xff1b;unload当页面完全卸载后在window上面触发&#xff0c;或当框架集卸载后在框架集上触发&#xff1b;select当用户选择文本框&#xff08;i…

笔记 | 嵌入式系统概论

1 嵌入式系统简介 1.1 嵌入式系统的定义 根据美国电气与电子工程师学会&#xff08;IEEE&#xff1a;Institute of Electrical and Electronics Engineers )的定义&#xff0c;嵌入式系统是用于控制、监视或辅助操作机器和设备的装置(原文: devices used to control, monitor…

07 内核开发-避免命名冲突经验技巧分享

07 内核开发-避免命名冲突经验技巧分享 目录 07 内核开发-避免命名冲突经验技巧分享 1.如何在内核开发过程中&#xff0c;避免命名冲突 2. 背景 3.避免方法 4.了解下 文件/proc/kallsyms 5.总结 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的…

redis主从

主从复制&#xff1a; 主从复制是Redis中常用的一种数据复制方式&#xff0c;它通过将一个Redis服务器&#xff08;主节点&#xff09;的数据复制到多个其他Redis服务器&#xff08;从节点&#xff09;上来实现数据的备份和读写分离。主从复制的主要作用是提高数据的可用性和读…

javaweb-SpringBoot基础

什么是SpringBoot Spring的官网(https://spring.io) Spring的简介&#xff1a;Spring makes Java simple。 Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率 。 SpringBootWeb快速入门 需求&#xff1a; 基于SpringBoot的方式开发一个web应用&#x…