这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天.
老师主要讲解了关于内存的分配与回收问题,在类似于Java语言中,具有垃圾自动回收机制。由于在之前代码中malloc函数中对应free函数,若不对开辟的内存空间进行回收,则最终程序会存在非常大的无用内存,若人工手动对垃圾进行回收,则可能造成多次回收等问题,如开辟了一次内存空间,但是释放了两次,则会使得代码产生错误。或当内存被释放后又重新被使用等问题。
所以为了解动态分配内存时管理回收机制则需要了解一些相关概念。
Mutator类似于开Go协程就会开始启用,Collector会回收内存空间。对于Serial GC,则是当多个go协程开启一段时间后全部暂停,等待单个Collector进行垃圾回收,回收后所有协程继续运行。在Parallel GC中当多个go协程开启一段时间后暂停,随后会有多个Collector同时回收,随后又全部开始协程运行,所以执行时间会有所提高。Concurrent GC中的协程开启和垃圾回收可以同时运行,当单个协程运行一段时间后进行暂停,利用垃圾回收开启运行,其他协程依旧正常运行,当垃圾回收结束后,暂停,原go协程继续运行,所以可以同时开启协程以及进行垃圾回收。
对于追踪垃圾回收,go将分配内存之间的节点进行关联,所有黑色节点都为扫描后的对象。若一次找到所有黑色节点,则白色节点将被回收,因为白色节点未被关联,无对象进行引用。一般标记的对象为静态变量,常量,全局变量,线程栈等。白色节点为不可到达的对象,需要被清理,清理的方式有多种,其中包括三种方式。
当白色对象被清除时,垃圾回收检测的多数都为需要被清除对象,只有少数需要保留。
所以copy GC是选择将需要保留的对象复制到其他的内存空间,最后将该块进行释放,可得到一个完整的空块。
但是有些需要保留的对象是长期保留的,如果经常需要复制到其他的内存空间则会极大的损耗性能,所以有第二种清除方式sweep GC,该清除保留一直存在的对象地址,将剩余白色区域清除,指针指向清除区域的首地址,该方法会使得剩余连续空间变小,但是可以保留一直需要被使用的的对象而不会进行频繁的复制操作。
第三种Compact GC方式则是将需要保留的对象复制到当前内存的首部并依次排列。
对于分代GC,其实是以上GC的结合方式,由于每个对象有年龄区分,经历GC次数越多则年龄越大,所以存在部分对象所在区域不需要大量的copy以损耗性能,所以分代GC将内存空间分为年轻块和老年块,老年块用sweep GC防止过度copy,而年轻块则由于对象很快用完需要被释放,所以利用copy GC将其清除。
随后课程介绍了引用计数概念,内存空间被变量所引用,则计数块+1,所以计数块也需要占据一定的内存,当有n个变量指向该内存块时,该内存块的计数块数值为n,当数值为0时,该内存块可进行释放,但是该方式无法检测环状引用,所以还是存在缺陷。
Go的内存分配
主要分为分块与缓存,Go首先会向操作系统申请一块较大的内存,随后将这些内存进行规整化划分,并按照每一定的大小将空间划分为若干块,对于接收到的对象,根据对应大小可以快速的找到合适的内存空间位置。
在缓存中,从开辟go协程空间开始,若存放的对象空间大小较大,预分配的空间不足,则会向mcenteral申请未被分配块的mspan,对于最后没有分配的mspan,会在缓存结束后释放归还至操作系统。
GO的内存状态,内存分配以及回收问题,需要从基本的数据结构形态开始,从堆栈信息,找到存放的变量信息,以及分析存放的规则,再对数据的存放,释放回收层层递进学习会有更清楚的认知,具体的内存分配等结构是十分复杂,需要参考老师推荐的文献进行慢慢深究。