GO--内存管理与分析|青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第9天
在后面会依次倒叙回顾之前的学习课程,便于复习~
一、课程重点内容
- 基本概念
- 垃圾回收机制
- 内存分配与优化
下面是对课程重点内容的思考与总结,有问题或者错误,可以批评指正呐~
二、基本概念
-
储备知识
-
在C语言中,内存管理主要管理的是
动态内存,动态内存指的是程序在运行时根据需求动态分配的内存,比如malloc()函数分配的内存。 -
内存管理也称为
垃圾回收,主要目的是由程序语言的运行时系统管理动态内存,这样做有以下好处:-
避免程序员进行内存管理,专注于实现业务逻辑
-
保证内存使用的正确性和安全性,比如
C语言中的内存多次释放:double-free problem, 释放后再次使用use-after-free problem。
-
-
三、垃圾回收机制
-
追踪垃圾回收(Tracing garbage collection)
- 策略:当一个对象的指针指向关系不可达的时候,该对象就要被回收了。
- 方法:
| 标记根对象 | 标记包括 静态变量、全局变量、常量、线程栈等 |
|---|---|
| 标记:找到可达对象 | 求指针指向关系的传递闭包:从根对象触发,找到所有可达对象 |
| 清理:所有不可达对象 | 将存活对象复制到另外的内存空间将死亡对象的内存标记为”可分配“移动并整理存活对象 |
-
引用计数(Reference counting)
-
策略:为每个对象都有一个与之相关联的引用数目,对象存活的条件为当且仅当引用数大于0。
-
优点:
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解
runtime的实现细节,有一些库可以帮助实现引用计数,比如C++智能指针(smart pointer)。
-
缺点:
- 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
- 无法回收环形数据结构
- 内存开销:每个对象都引入的额外内存空间存储引用数目
-
四、内存分配与优化
-
基本概念:
- 分配内存
初始化新进程时,运行时会为进程保留一个连续的地址空间区域。 这个保留的地址空间被称为托管堆。 托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。
最初,该指针设置为指向托管堆的基址。 托管堆上包含了所有引用类型。 应用程序创建第一个引用类型时,将为托管堆的基址中的类型分配内存。 应用程序创建下一个对象时,垃圾回收器在紧接第一个对象后面的地址空间内为它分配内存。 只要地址空间可用,垃圾回收器就会继续以这种方式为新对象分配空间。
- 释放内存
垃圾回收器的优化引擎根据所执行的分配决定执行回收的最佳时间。 垃圾回收器在执行回收时,会释放应用程序不再使用的对象的内存。 它通过检查应用程序的根来确定不再使用的对象。
每个应用程序都有一组根。 每个根或者引用托管堆中的对象,或者设置为空。 应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及 CPU 寄存器。 垃圾回收器可以访问由实时 (JIT) 编译器和运行时维护的活动根的列表。 垃圾回收器对照此列表检查应用程序的根,并在此过程中创建一个图表,在其中包含所有可从这些根中访问的对象。
-
分块与缓存
-
分块
-
Go的内存分配:
Go是提前将内存分成一个一个的小块,当创建对象的时候,在内存中找到一个与对象尺寸最接近的一个块分配给他,就完成了一次内存分配。 -
步骤:调用系统调用
mmap()向OS申请一大块内存,例如4MB先将内存划分大块,例如
8KB,称为mspan;再将大块继续划分成特定大小的小块,用于对象分配;noscan mspan:分配不包含指针的对象—GC不需要扫描;scan mspan:分配包括指针的对象—GC需要扫描
-
-
缓存
-
Go的内存分配借鉴了TCMalloc(Thread Caching)内存分配器的实现。 -
步骤:
-
Go在分配内存的时候,都是Goroutine上面执行的代码去分配一块内存,如下图所示,从g出发,找到m和p(不懂啥意思??),在p上有一个数据接口mcache,在mcache中存了一组mspans,每个mspans的大小是不一样的。在
mspans里面找到一个最合适的mspan里的一个空余的块,找到这个块之后,返回出去,就完成了一次对象的分配。如果
mcache里的mspan都是满的,那就就会从下一个级别的缓存,也就是mcentral里面找一个带有空余空间的mspan,并将其填充的mcache里面去,然后再继续分配。
-
-
五、课程总结
- 介绍了自动内存管理的意义
- 介绍了几种自动内存管理算法
- 介绍了Go的内存管理方案