这是我参与「第五届青训营 」伴学笔记创作活动的第五天。今天学习了go语言的内存管理,因为和jvm的内存管理有很多相似的地方,所以今天的学习较之前轻松一点。
自动内存管理
于java一样程序会在运行时自动根据需求自动分配内存,这样避免了程序员手动管理内存,可以更专心于业务逻辑。常见的三种垃圾收集器:
- Serial GC:该垃圾收集器是串行的垃圾收集器,适合使用在单核场景,会发生STW。
- Parallel GC:此为支持并行的垃圾收集器,适合使用在多核的场景,也会发生STW。
- Concourent GC:此垃圾收集器可以做到回收垃圾时GC线程与业务线程并行执行,虽然也会发生STW,但可以通过分布标记大大减少STW的时间。
我们可以从四个维度综合分析垃圾收集算法的性能:
- 安全性:这是作为垃圾收集算法的基础条件,它确保存活的对象不会被回收。
- 吞吐率:1-GC时间/总时间,该指标反映花费在GC上的时间,该数值越大表示GC的时间越长,单位时间内处理的业务也就越少。
- 暂停时间:该指标为STW暂用的时间,该数值越大程序的卡顿越明显。
- 内存开销:GC对内存的消耗,越小越好。
垃圾追踪与回收
垃圾收集器处理的对象是被程序认为垃圾的对象,那什么样的对象会被认为是垃圾呢?有两种算法帮助程序去寻找垃圾对象:引用计数算法与可达性分析算法。
可达性分析与引用计数
先来说一下可达性分析算法,该算法会将程序中的常量、静态变量、静态常量等作为GCRoot对象,从这些对象出发,可以直接或间接被GCRoot对象的指针指到的对象为可到达的对象,该算法会对这些对象打上可达的标记,并在后续清理时跳过这些对象,并且会根据对象生命周期的不同采用不同的标记和清理策略。引用计数算法是通过代表引用数量的属性来发现垃圾的,每个对象都会维护一个记录与自己关联的引用数目的属性,当该属性为0时代表没有引用指向自己,自己也就成为了垃圾,该算法的优点在于可以在程序运行时实时监控垃圾对象,不需要每次都遍历所有可达对象,内存管理也不需要知道runtinme的实现细节,当然缺点也很明显,要给程序中所有的对象添加一个属性,并保证对其操作的原子性,会增加程序的开销,并且该算法无法解决循环引用的问题。
复制算法与标记压缩算法
常见的垃圾回收算法有两种,分别是标记压缩算法与复制算法,先来说说复制算法,该算法会把空间分为两部分,一部分用来保存存活对象,另一部分什么都不保存等待接收对象,当保存对象的部分存满时会触发垃圾回收,将回收后存活的对象复制到另一部分中,并按顺序存放,避免了碎片化的问题。标记压缩算法不会将空间分开,它会在全部空间内进行垃圾回收,并将存活的对象在原空间内重新按顺序排列,避免碎片化的问题。
分代GC
与Java中的分代思想类似,将整个内存空间分为了新生代与老年代,按照对象不同年龄分配在不同的区域中,新创建的对象会被分配到新生代,经过一系列的GC后如果还在存活代表其为长期对象,此时会将其移动到老年代,。针对不同的区域使用不同的垃圾回收算法可以减少内存的开销,对于年轻代来说大部分的对象都是朝生夕死的适合使用复制算法,因为新生对象绝大部分都会保存在新生代,并且生存的时间都很短,每次GC都会清除大于八成的对象,所以导致新生代触发GC的频率会很大,剩余对象很少,在这种情况下复制算法会更适合,因为存活对象较少时单次复制比单次压缩效率高。而老年代适合标记压缩算法,这是因为;老年代绝大部分都是些需要长期存活的对象,每次GC回收的垃圾并不多,不能使用复制算法对如此多的对象进行复制,而是使用整理将空出来的碎片做修补。