这是我参与「第五届青训营 」笔记创作活动的第4天
一、本堂课重点内容:
- 学习了自动内存管理机制和Go语言的内存管理机制
- 了解了编译器的工作原理
二、详细知识点介绍:
1.自动内存管理机制:
自动内存管理机制是对于动态分配的内存而言的,就是堆上的内存。对于堆上分配的内存,需要有相应的回收机制,把他归还给操作系统,以前的C和C++是没有这种自动内存管理机制的,就需要手动去delete或者free。所以有了这种垃圾回收机制后:就可以避免我们手动释放内存,专注于业务逻辑实现。保证内存使用的正确性和安全性:不会出现使用已经归还给操作系统的内存的情况。
自动内存管理的任务:1)为新对象分配空间。2)找到存活对象。3)回收死亡对象的内存空间。
接着介绍几个垃圾回收机制的相关概念:
- Mutator:业务线程,分配新对象,修改对象指向关系
- Collection:GC线程,找到存活的对象,回收死亡的对象的内存空间。
- Serial GC:只有一个GC线程,单线程回收
- Paraller GC:支持多个线程同时回收
- Concurrent GC:支持业务线程和GC线程同时执行,使得回收过程不会Stop work,可以优化性能。
GC算法介绍:
- 追踪垃圾回收机制:这种机制会首先判断存活的对象,根据存活的对象判断可以其余所有的对象为垃圾对象,判断出垃圾对象后然后进行回收。追踪垃圾回收机制本身包括三个算法:标记-清除(Mark-Sweep):利用free-list来管理该块内存中活着的对象、标记-复制(Mark-Copy):把该块内存中活着的对象复制给另一块内存、标记-整理(Mark-Compact):在该块内存空间中整理活着的对象。这三种策略需要根据对象的生命周期不同,使用不同的策略。例如使用分代假说,将对象分为old和young,对old对象可以使用标记清除策略,对young对象使用标记复制策略。
- 引用计数回收机制:引用计数回收机制,本质上是对每个对象使用引用计数器来标识有几个指针指向该对象,因为使用了标识,所以引用计数回收机制不可避免的会增大内存的消耗。引用计数回收机制会回收计数为0的对象的内存空间。因为该对象可以被多线程所操作,如果线程1解除了对对象的引用,使得对象的引用计数变为0,然后被回收,此时线程2执行,由于线程2并不知道该对象被回收了,而使用了已经被回收的内存。为了避免这种情况的发生。引用计数回收机制要保证原子性。
2.Go内存管理机制:
在GO语言中,会在堆上提前申请一大块内存,并将内存分成较大块(mspan),然后再分成特定大小的小块,以便后续使用,避免了频繁地系统调用,然后根据你所申请的对象的大小,去选择一个块大小近似的内存空间并返回。如果所分配的对象中不包含指针,就不需要被GC线程所扫描到,因为你没有包含指向其他对象的指针,反之,则需要被GC线程所扫描。除了分块机制以外,Go语言还有缓存机制,当一个线程g请求一个对象之后,会根据g找到p,并使用p中的mcache来快速分配,mcache会管理mspan,如果mcache分配完毕,则会向mcentral中申请新的mspan,当mspan中没有可以分配的对象了,并不会立刻归还给操作系统,而是先缓存一段时间。
3.编译器工作原理:
首先会根据代码进行词法分析,生成词素。然后根据语法分析,生成语法树。接着根据语义分析,收集类型信息,进行语义检查。最后生成中间代码IR。在后端部分,会进行代码优化,生成优化后的IR,最终生成目标代码。
函数内联:函数内联,就是用函数体的副本去替代函数调用。,函数内联容易代码膨胀,但是对于简单的代码使用函数内联可以加快程序的性能。除此之外,使用函数内联能便于我们进行过程间的分析以及逃逸分析。所谓逃逸分析,就是指指针逃脱了作用域,被其他的函数所使用了,通过函数内联可以减少一部分的指针逃逸。
三、实践练习例子:
看了BeastMode来对编译器进行优化,适当根据策略使用函数内联,提高了性能。
四、课后个人总结:
- 在本章的学习中,了解了内存管理机制的相关算法,和Go语言是如何管理内存的,学习到了编译器的工作原理以及函数内联的相关分析。
- 对于编译器工作原理的细节不是太了解,只是知道大概流程。