面试题答案
一键面试标记阶段操作
- 初始化标记:
- 暂停所有用户协程(STW,Stop The World),这一步是为了确保在标记过程中对象的引用关系不会发生变化,避免误判垃圾对象。
- 重置所有堆对象的标记位,将所有对象标记为未标记状态,为后续准确标记可达对象做准备。
- 根对象扫描:
- 从根对象集合开始扫描,根对象包括全局变量、栈上的变量等。这些根对象是程序能够直接访问到的对象,是对象可达性的起始点。
- 对于每个根对象,标记其引用的所有对象,将这些对象标记为已标记状态。
- 并发标记:
- 在完成根对象扫描后,恢复用户协程的执行(此时是并发标记阶段,用户协程和标记操作并发执行)。
- 标记器从根对象出发,通过对象间的引用关系,逐步标记所有可达对象。标记器在扫描对象时,会沿着对象的指针找到其引用的其他对象,并将这些对象标记为已标记。
- 为了处理在并发标记过程中用户协程新创建或修改的对象引用关系,Go语言采用写屏障技术。写屏障会记录新的对象引用关系,确保在并发标记过程中不会遗漏可达对象。例如,当一个对象的指针字段被修改时,写屏障会将新指向的对象记录下来,以便标记器后续能够正确标记该对象。
- 重新扫描栈:
- 再次暂停用户协程(STW)。这是因为在并发标记阶段,用户协程的执行可能导致栈上的对象引用关系发生变化。
- 重新扫描栈,标记栈上新增的可达对象。在并发标记阶段,栈上可能有新创建的对象或者对象的引用关系发生了改变,重新扫描栈可以保证这些新的可达对象被正确标记。
对垃圾回收整体流程的意义
- 准确识别可达对象:标记阶段通过从根对象出发,递归标记所有可达对象,使得垃圾回收器能够准确地知道哪些对象是正在被使用的(可达对象),哪些对象不再被任何可达对象引用(不可达对象,即垃圾对象)。这为后续垃圾回收的清除阶段提供了明确的对象标记依据,只有未被标记的对象(垃圾对象)才会在清除阶段被回收。
- 控制STW时间:通过并发标记,Go语言在一定程度上减少了垃圾回收过程中STW的时间。虽然在初始化标记和重新扫描栈时仍然需要STW,但并发标记允许在大部分时间里用户协程和标记操作同时进行,减少了垃圾回收对应用程序性能的影响。如果没有并发标记,整个标记过程都在STW状态下进行,会导致应用程序长时间停顿,严重影响用户体验和系统的整体性能。
- 保证对象引用关系的一致性:写屏障技术在并发标记过程中保证了对象引用关系的记录完整性。即使在用户协程并发修改对象引用关系的情况下,写屏障也能确保标记器不会遗漏新的可达对象,从而保证了垃圾回收的正确性。如果没有写屏障,在并发标记时用户协程修改对象引用可能导致部分可达对象未被标记,进而在清除阶段被错误地回收,造成程序运行错误。