用户程序通过内存分配器(Allocator)在堆上申请内存,而垃圾收集器(Collector)负责回收堆上的内存空间,内存分配器和垃圾收集器共同管理程序中的堆内存空间。

基本概念

垃圾分类

  • 语义垃圾:也就是内存泄漏,指的是从语法上可达的对象,也就是被其他对象引用的,但是从语义上来讲是垃圾。这类垃圾,垃圾回收是不管的
  • 语法垃圾:从语法上是不可达的对象,也就是没有对象引用了,这些垃圾是垃圾回收重点照顾的对象

垃圾回收算法

常见的垃圾回收算法:

  • 引用计数:

    某个对象的根引用计数变为0时,其所有节点均需被回收

  • 标记压缩:

    存活对象移动到一起,有效解决内存碎片问题

  • 复制算法:

    将所有正在使用的对象从From空间复制到To空间,堆利用率只有一半,也能解决内存碎片问题

  • 标记清除:

    标记垃圾对象,然后再清除。解决不了内存碎片问题,需要与能尽量避免内存碎片的内存分配器使用,如tcmalloc

标记清除

标记清除算法是最常见的垃圾收集算法,标记清除收集器是跟踪式垃圾收集器,其执行过程可以分为标记、清除两个阶段:

  1. 标记阶段:从根对象出发查找并标记堆中所有存活的对象
  2. 清除阶段:遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表

传统的标记清除算法,垃圾收集器从垃圾收集的根对象出发,递归遍历这些对象指向的子对象并将所有可达的对象标记成存活。标记结束后,垃圾收集器会依次遍历堆中的对象并清除其中的垃圾,整个过程需要标记对象的存活状态,所以用户程序在垃圾收集的过程中不能执行,也就是常说的STW(Stop The World)

三色抽象

为了解决原始标记清除算法带来的长时间STW,大部分追踪式垃圾收集器都会实现三色标记算法的变种以缩短STW的时间

三色标记算法将程序中的对象分成白色、黑色、灰色:

  • 白色对象:潜在的垃圾,也就是没有被扫到的对象,其内存可能会被垃圾收集器回收
  • 黑色对象:活跃的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象
  • 灰色对象:活跃的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象

标记过程:

垃圾收集器开始工作的时候,不存在任何黑色对象,根对象会被标记成灰色,垃圾收集器只会从灰色对象集合中取出对象开始扫描,当灰色集合中不存在任何对象时,标记就会结束。

大致工作原理:

  1. 从灰色对象的集合中选择一个灰色对象并将其标记成黑色
  2. 将黑色对象指向的所有对象都标记成灰色,保证该对象和被该对象引用的对象都不会被回收
  3. 重复上述两个步骤直到不存在灰色对象

当三色标记结束后,应用程序的堆中就不存在任何灰色对象,我们只能看到黑色的存活对象以及白色的垃圾对象,垃圾收集器可以回收这些白色的垃圾。

缺陷:

由于用户程序可能在标记执行的过程中修改对象的指针,所以三色标记清除算法本身是不可以并发或者增量执行的,仍需要STW。如果本来不应该被回收的对象被回收了,这在内存管理中是非常严重的错误,这种错误称为悬挂指针,也就是指针没有指向特定类型的合法对象,影响了内存的安全性,想要并发或者增量标记对象就需要使用屏障技术。

屏障技术

内存屏障技术是一种屏障指令,可以让CPU或编译器在执行内存相关的操作时遵循特定的约束,目前多数的现代处理器都会乱序执行指令以最大化性能,但是该技术能够保证内存操作的顺序性,在内存屏障前执行的操作一定会先于内存屏障后执行的操作。想在并发或增量的标记算法中保证正确性,需要达到两种三色不变性的其中一种。

三色不变性:

  • 强三色不变性:黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象
  • 弱三色不变性:黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径

屏障技术分为读屏障和写屏障,但是由于读屏障需要在读操作中加入代码片段,所以对用户程序的性能影响较大。解析一下go语言中使用的两种写屏障技术,插入写屏障和删除写屏障。

插入写屏障(DIJKSTRA)

writePointer(slot, ptr):
shade(ptr)
*slot = ptr

每当要执行*slot = ptr时,会先执行写屏障通过shade函数尝试改变指针颜色。如果ptr指针是白色,那么会将该对象设置成灰色。

这张图中可以看到,在一次正常的标记过程中,发生了用户程序修改了指针引用(或者新插入了一个引用关系)的情况,如果我们采用 插入写屏障 我们就需要将新指向的对象标为灰色,以此保证强三色不变性。(对于新指向的对象来说,属于被插入一个引用,所以叫插入写屏障)

在插入写屏障中,我们的标记过程变成(关键第2步):

  1. 基本标记流程不变,我们快进到垃圾收集器将根对象指向A对象标记成黑色,并将A对象指向的对象B标记成灰色。
  2. 这时用户程序修改A对象的指针,将原本A对象指向B对象的指针指向C对象,这个时候就会触发写屏障将C对象标记为灰色,避免被错误回收。
  3. 垃圾收集器依次遍历程序中的其他灰色对象,将他们分别标记成黑色

删除写屏障(YUASA)

writePointer(slot, ptr)
shade(*slot)
*slot = ptr

删除写屏障会在老对象的引用被删除的时候,将白色的老对象涂成灰色,这样就可以保证弱三色不变性,老对象引用的下游对象一定可以被灰色对象引用。

使用删除写屏障技术的垃圾收集器和用户程序交替运行的场景中的标记过程:

  1. 垃圾收集器将根对象指向A对象标记成黑色并将A对象指向的对象B标记成灰色
  2. 如果这时用户程序将B指向C的指针删除,那么C触发删除写屏障,由于C是白色,所以被涂成灰色
  3. 垃圾收集器依次遍历程序中的其他灰色对象,将他们分别标记成黑色

第二步触发删除写屏障的着色,因为删除了B指向C的指针,所以C和D分别违反强三色不变性和弱三色不变性,着色后保证了三色不变性,避免悬挂指针。

简单来说就是,在改变指针指向的时候,原来指向的那个对象是白色的话就要变成灰色,以此保证弱三色不变性。(对于老对象来说,引用关系被解除了,所以叫删除写屏障)

增量和并发

两种策略优化垃圾收集器不会以为回收垃圾导致长时间STW:

  • 增量垃圾收集:增量得标记和清除垃圾,降低应用程序暂停的最长时间
  • 并发垃圾手机:利用多核的计算资源,在用户程序执行时并发标记和清除垃圾

由于两种方式都需要垃圾收集器与用户程序交替执行,所以需要配合屏障技术。

增量收集器

增量收集器将原本时间较长的暂停时间切分成多个更小的GC时间片。增量收集器需要配合三色标记法和屏障技术一起使用。将GC过程分段执行,虽然拉长了总的垃圾回收时间,但是减少了程序STW的时间。不过写屏障还是有些开销的。

并发收集器

利用多核优势,将GC过程与用户程序并行执行(大部分情况下),也是要配合屏障技术。

References

  1. https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/
  2. https://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/

GO的GC辣鸡回收(一)的更多相关文章

  1. JVM 辣鸡回收

    垃圾回收算法 标记清除法 先标记出需要回收的对象,然后一次性回收.缺点:会产生内存碎片,并且效率也不高. 标记压缩法 先标记出需要回收的对象,然后让存活对象向一端移动,移动的过程中进行回收辣鸡.避免了 ...

  2. JVM学习(4)——全面总结Java的GC算法和回收机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  3. java: system.gc()和垃圾回收机制finalize

    System.gc()和垃圾回收机制前的收尾方法:finalize(收尾机制) 程序退出时,为每个对象调用一次finalize方法,垃圾回收前的收尾方法 System.gc() 垃圾回收方法 clas ...

  4. 关于GC进行垃圾回收的时机

    前言 今天查看一个同事的代码,发现代码中多处地方使用了GC.Collect()方法,我问他为什么这么做,他说感觉程序中定义了好多变量,怕GC回收不及时,用GC.Collect()可以手动掌控GC进行垃 ...

  5. GC(垃圾回收)

    Java程序的内存分配和回收都是由JRE在后台自动进行的.JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收GC.通常JRE会提供一条超级线程来进行检测和控制,一般都是在CPU空闲或内存不足时 ...

  6. 【转载】Java性能优化之JVM GC(垃圾回收机制)

    文章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我 ...

  7. JVM总括二-垃圾回收:GC Roots、回收算法、回收器

    JVM总括二-垃圾回收:GC Roots.回收算法.回收器 目录:JVM总括:目录 一.判断对象是否存活 为了判断对象是否存活引入GC Roots,如果一个对象与GC Roots没有直接或间接的引用关 ...

  8. JVM学习(4)——全面总结Java的GC算法和回收机制---转载自http://www.cnblogs.com/kubixuesheng/p/5208647.html

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  9. Java性能优化之JVM GC(垃圾回收机制)

    Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.st ...

随机推荐

  1. 记一次Vue跨导航栏问题解决方案

    简述 这篇文章是我项目中,遇到的一个issue,我将解决过程和方法记录下来. 本篇文章基于Vue.js进行的前端页面构建,由于仅涉及前端,将不做数据来源及其他部分的叙述.使用的CSS框架是 Boots ...

  2. odoo里的rpc用法

    import odoorpcdb_name = 'test-12'user_name = 'admin'password = 'admin'# Prepare the connection to th ...

  3. LinkedHashMap 的实现原理

    LinkedHashMap 概述 HashMap 是无序的,HashMap 在 put 的时候是根据 key 的 hashcode 进行 hash 然后放入对应的地方.所以在按照一定顺序 put 进 ...

  4. 大数据学习(10)—— Hive进阶

    前面提到了Hive的知识点非常零散,我不知道该怎么把这些知识点分类,跟SQL关系没那么大的就放在这一篇吧. Hive Serde 参考Hive Serde Serde是啥 Serde是序列化和反序列化 ...

  5. Python小白的数学建模课-12.非线性规划

    非线性规划是指目标函数或约束条件中包含非线性函数的规划问题,实际就是非线性最优化问题. 从线性规划到非线性规划,不仅是数学方法的差异,更是解决问题的思想方法的转变. 非线性规划问题没有统一的通用方法, ...

  6. html拖动元素排序(插件版)

    1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 < ...

  7. 【GCC编译器】计算支配树信息 Part1 - 求CFG的深度为主搜索树

    深度为主生成树:将图中所有的结点和那些构成深度为主次序的边表示为树的形式,并将其他的边(这些边不是深度为主次序的一部分)用一种有别于树的方式来表示(我们用虚线而不是实线表示它们) 属于深度为主生成树的 ...

  8. Python实现猜数字游戏

    Python中实现猜数字游戏代码如下: import random # 引入随机数标准库-random # 定义数字上下限和最大游戏次数 min_num = 1 max_num = 10 guess_ ...

  9. WPS函数

    vlookup函数:=VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) 官方解释:其逻辑为在某一区间内搜索区间外某一单元格的值, ...

  10. oracle、postgres、mysql数据库的建库、创建用户、导人导出备份总结

    本文包含的内容:使用命令操作oracle.postgres.mysql的导入导出,登录到数据,创建用户 注:我在公司使用的是Center OS操作系统,所以oracle和postgres均是在Linu ...